From 18b2d40e658401d6bf251c8ce8494f6a056c7fa6 Mon Sep 17 00:00:00 2001 From: Alexey Shibanov <83034617+alexeyshibanov@users.noreply.github.com> Date: Tue, 9 Dec 2025 20:22:26 +0300 Subject: [PATCH 1/8] feat: Refactor CustomerOrderChangesProvider and IndexCustomerOrderChangedEventHandler for improved readability, maintainability and extensibility --- .../IndexCustomerOrderChangedEventHandler.cs | 64 ++++++++++---- .../Indexed/CustomerOrderChangesProvider.cs | 85 +++++++++---------- 2 files changed, 89 insertions(+), 60 deletions(-) diff --git a/src/VirtoCommerce.OrdersModule.Data/Handlers/IndexCustomerOrderChangedEventHandler.cs b/src/VirtoCommerce.OrdersModule.Data/Handlers/IndexCustomerOrderChangedEventHandler.cs index aaf7abd75..66285494f 100644 --- a/src/VirtoCommerce.OrdersModule.Data/Handlers/IndexCustomerOrderChangedEventHandler.cs +++ b/src/VirtoCommerce.OrdersModule.Data/Handlers/IndexCustomerOrderChangedEventHandler.cs @@ -1,4 +1,3 @@ -using System; using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; @@ -18,10 +17,13 @@ namespace VirtoCommerce.OrdersModule.Data.Handlers { public class IndexCustomerOrderChangedEventHandler : IEventHandler { - private readonly ISettingsManager _settingsManager; - private readonly IConfiguration _configuration; - private readonly IIndexingJobService _indexingJobService; - private readonly IEnumerable _indexingConfigurations; + protected ISettingsManager SettingsManager { get; } + + protected IConfiguration Configuration { get; } + + protected IIndexingJobService IndexingJobService { get; } + + protected IEnumerable IndexingConfigurations { get; } public IndexCustomerOrderChangedEventHandler( ISettingsManager settingsManager, @@ -29,26 +31,54 @@ public IndexCustomerOrderChangedEventHandler( IIndexingJobService indexingJobService, IEnumerable indexingConfigurations) { - _settingsManager = settingsManager; - _configuration = configuration; - _indexingJobService = indexingJobService; - _indexingConfigurations = indexingConfigurations; + SettingsManager = settingsManager; + Configuration = configuration; + IndexingJobService = indexingJobService; + IndexingConfigurations = indexingConfigurations; } - public async Task Handle(OrderChangedEvent message) + public virtual async Task Handle(OrderChangedEvent message) { - if (!_configuration.IsOrderFullTextSearchEnabled() || - !await _settingsManager.GetValueAsync(ModuleConstants.Settings.General.EventBasedIndexation)) + if (!await ShouldIndexAsync()) { return; } - var indexEntries = message?.ChangedEntries - .Select(x => new IndexEntry { Id = x.OldEntry.Id, EntryState = x.EntryState, Type = ModuleConstants.OrderIndexDocumentType }) - .ToArray() ?? Array.Empty(); + await IndexOrdersAsync(message); + } + + protected virtual async Task ShouldIndexAsync() + { + return Configuration.IsOrderFullTextSearchEnabled() && + await SettingsManager.GetValueAsync(ModuleConstants.Settings.General.EventBasedIndexation); + } + + protected virtual Task IndexOrdersAsync(OrderChangedEvent message) + { + var indexEntries = GetOrderIndexEntries(message); + + if (indexEntries.Length > 0) + { + var documentBuilders = IndexingConfigurations + .GetDocumentBuilders(ModuleConstants.OrderIndexDocumentType, typeof(CustomerOrderChangesProvider)) + .ToList(); + + IndexingJobService.EnqueueIndexAndDeleteDocuments(indexEntries, JobPriority.Normal, documentBuilders); + } + + return Task.CompletedTask; + } - _indexingJobService.EnqueueIndexAndDeleteDocuments(indexEntries, - JobPriority.Normal, _indexingConfigurations.GetDocumentBuilders(ModuleConstants.OrderIndexDocumentType, typeof(CustomerOrderChangesProvider)).ToList()); + protected virtual IndexEntry[] GetOrderIndexEntries(OrderChangedEvent message) + { + return message?.ChangedEntries + .Select(x => new IndexEntry + { + Id = x.OldEntry.Id, + EntryState = x.EntryState, + Type = ModuleConstants.OrderIndexDocumentType, + }) + .ToArray() ?? []; } } } diff --git a/src/VirtoCommerce.OrdersModule.Data/Search/Indexed/CustomerOrderChangesProvider.cs b/src/VirtoCommerce.OrdersModule.Data/Search/Indexed/CustomerOrderChangesProvider.cs index e38acab67..eda0c9f72 100644 --- a/src/VirtoCommerce.OrdersModule.Data/Search/Indexed/CustomerOrderChangesProvider.cs +++ b/src/VirtoCommerce.OrdersModule.Data/Search/Indexed/CustomerOrderChangesProvider.cs @@ -2,8 +2,6 @@ using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; -using VirtoCommerce.CatalogModule.Core.Model; -using VirtoCommerce.CustomerModule.Core.Model; using VirtoCommerce.OrdersModule.Core.Model; using VirtoCommerce.OrdersModule.Data.Repositories; using VirtoCommerce.Platform.Core.ChangeLog; @@ -17,83 +15,84 @@ public class CustomerOrderChangesProvider : IIndexDocumentChangesProvider { public const string ChangeLogObjectType = nameof(CustomerOrder); - private readonly Func _orderRepositoryFactory; - private readonly IChangeLogSearchService _changeLogSearchService; + protected Func OrderRepositoryFactory { get; } + + protected IChangeLogSearchService ChangeLogSearchService { get; } public CustomerOrderChangesProvider(Func orderRepositoryFactory, IChangeLogSearchService changeLogSearchService) { - _orderRepositoryFactory = orderRepositoryFactory; - _changeLogSearchService = changeLogSearchService; + OrderRepositoryFactory = orderRepositoryFactory; + ChangeLogSearchService = changeLogSearchService; } - public async Task> GetChangesAsync(DateTime? startDate, DateTime? endDate, long skip, long take) + public virtual async Task> GetChangesAsync(DateTime? startDate, DateTime? endDate, long skip, long take) { if (startDate == null && endDate == null) { return GetChangesFromRepository(skip, take); } - return await GetChangesFromOperaionLog(startDate, endDate, skip, take); + return await GetChangesFromOperationLog(startDate, endDate, skip, take); } - public async Task GetTotalChangesCountAsync(DateTime? startDate, DateTime? endDate) + public virtual async Task GetTotalChangesCountAsync(DateTime? startDate, DateTime? endDate) { if (startDate == null && endDate == null) { // Get total products count - using (var repository = _orderRepositoryFactory()) - { - return repository.CustomerOrders.Count(); - } + using var repository = OrderRepositoryFactory(); + + return repository.CustomerOrders.Count(); } var criteria = GetChangeLogSearchCriteria(startDate, endDate, 0, 0); // Get changes count from operation log - return (await _changeLogSearchService.SearchAsync(criteria)).TotalCount; + return (await ChangeLogSearchService.SearchAsync(criteria)).TotalCount; } /// /// Get documents from repository and return them as changes /// - private IList GetChangesFromRepository(long skip, long take) + protected virtual IList GetChangesFromRepository(long skip, long take) { - using (var repository = _orderRepositoryFactory()) - { - var productIds = repository.CustomerOrders - .OrderBy(i => i.CreatedDate) - .Select(i => i.Id) - .Skip((int)skip) - .Take((int)take) - .ToArray(); - - return productIds.Select(id => + using var repository = OrderRepositoryFactory(); + + var productIds = repository.CustomerOrders + .OrderBy(x => x.CreatedDate) + .Select(x => new { x.Id, ModifiedDate = x.ModifiedDate ?? x.CreatedDate }) + .Skip((int)skip) + .Take((int)take) + .ToArray(); + + return productIds + .Select(x => new IndexDocumentChange { - DocumentId = id, + DocumentId = x.Id, ChangeType = IndexDocumentChangeType.Modified, - ChangeDate = DateTime.UtcNow - } - ).ToArray(); - } + ChangeDate = x.ModifiedDate, + }) + .ToArray(); } /// /// Get changes from operation log /// - private async Task> GetChangesFromOperaionLog(DateTime? startDate, DateTime? endDate, long skip, long take) + protected virtual async Task> GetChangesFromOperationLog(DateTime? startDate, DateTime? endDate, long skip, long take) { var criteria = GetChangeLogSearchCriteria(startDate, endDate, skip, take); - var operations = (await _changeLogSearchService.SearchAsync(criteria)).Results; - - return operations.Select(o => - new IndexDocumentChange - { - DocumentId = o.ObjectId, - ChangeType = o.OperationType == EntryState.Deleted ? IndexDocumentChangeType.Deleted : IndexDocumentChangeType.Modified, - ChangeDate = o.ModifiedDate ?? o.CreatedDate, - } - ).ToArray(); + var operations = (await ChangeLogSearchService.SearchAsync(criteria)).Results; + + return operations + .Select(x => + new IndexDocumentChange + { + DocumentId = x.ObjectId, + ChangeType = x.OperationType == EntryState.Deleted ? IndexDocumentChangeType.Deleted : IndexDocumentChangeType.Modified, + ChangeDate = x.ModifiedDate ?? x.CreatedDate, + }) + .ToArray(); } protected virtual ChangeLogSearchCriteria GetChangeLogSearchCriteria(DateTime? startDate, DateTime? endDate, long skip, long take) @@ -104,12 +103,12 @@ protected virtual ChangeLogSearchCriteria GetChangeLogSearchCriteria(DateTime? s if (types.Count != 0) { - types.Add(nameof(CustomerOrder)); + types.Add(ChangeLogObjectType); criteria.ObjectTypes = types; } else { - criteria.ObjectType = nameof(CustomerOrder); + criteria.ObjectType = ChangeLogObjectType; } criteria.StartDate = startDate; From d51a8c32e36de1bb212f2c7f0b946019843fd2c8 Mon Sep 17 00:00:00 2001 From: Alexey Shibanov <83034617+alexeyshibanov@users.noreply.github.com> Date: Thu, 11 Dec 2025 13:45:27 +0300 Subject: [PATCH 2/8] feat: Update CustomerOrderChangesProvider to use async methods for improved performance --- .../Indexed/CustomerOrderChangesProvider.cs | 22 +++++++++---------- 1 file changed, 10 insertions(+), 12 deletions(-) diff --git a/src/VirtoCommerce.OrdersModule.Data/Search/Indexed/CustomerOrderChangesProvider.cs b/src/VirtoCommerce.OrdersModule.Data/Search/Indexed/CustomerOrderChangesProvider.cs index eda0c9f72..0332054df 100644 --- a/src/VirtoCommerce.OrdersModule.Data/Search/Indexed/CustomerOrderChangesProvider.cs +++ b/src/VirtoCommerce.OrdersModule.Data/Search/Indexed/CustomerOrderChangesProvider.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; +using Microsoft.EntityFrameworkCore; using VirtoCommerce.OrdersModule.Core.Model; using VirtoCommerce.OrdersModule.Data.Repositories; using VirtoCommerce.Platform.Core.ChangeLog; @@ -25,14 +26,11 @@ public CustomerOrderChangesProvider(Func orderRepositoryFactor ChangeLogSearchService = changeLogSearchService; } - public virtual async Task> GetChangesAsync(DateTime? startDate, DateTime? endDate, long skip, long take) + public virtual Task> GetChangesAsync(DateTime? startDate, DateTime? endDate, long skip, long take) { - if (startDate == null && endDate == null) - { - return GetChangesFromRepository(skip, take); - } - - return await GetChangesFromOperationLog(startDate, endDate, skip, take); + return startDate == null && endDate == null + ? GetChangesFromRepositoryAsync(skip, take) + : GetChangesFromOperationLogAsync(startDate, endDate, skip, take); } public virtual async Task GetTotalChangesCountAsync(DateTime? startDate, DateTime? endDate) @@ -42,7 +40,7 @@ public virtual async Task GetTotalChangesCountAsync(DateTime? startDate, D // Get total products count using var repository = OrderRepositoryFactory(); - return repository.CustomerOrders.Count(); + return await repository.CustomerOrders.CountAsync(); } var criteria = GetChangeLogSearchCriteria(startDate, endDate, 0, 0); @@ -54,16 +52,16 @@ public virtual async Task GetTotalChangesCountAsync(DateTime? startDate, D /// /// Get documents from repository and return them as changes /// - protected virtual IList GetChangesFromRepository(long skip, long take) + protected virtual async Task> GetChangesFromRepositoryAsync(long skip, long take) { using var repository = OrderRepositoryFactory(); - var productIds = repository.CustomerOrders + var productIds = await repository.CustomerOrders .OrderBy(x => x.CreatedDate) .Select(x => new { x.Id, ModifiedDate = x.ModifiedDate ?? x.CreatedDate }) .Skip((int)skip) .Take((int)take) - .ToArray(); + .ToArrayAsync(); return productIds .Select(x => @@ -79,7 +77,7 @@ protected virtual IList GetChangesFromRepository(long skip, /// /// Get changes from operation log /// - protected virtual async Task> GetChangesFromOperationLog(DateTime? startDate, DateTime? endDate, long skip, long take) + protected virtual async Task> GetChangesFromOperationLogAsync(DateTime? startDate, DateTime? endDate, long skip, long take) { var criteria = GetChangeLogSearchCriteria(startDate, endDate, skip, take); var operations = (await ChangeLogSearchService.SearchAsync(criteria)).Results; From 7b6e21a76184c386aa1bc4dfa82ebc85ac11454b Mon Sep 17 00:00:00 2001 From: Alexey Shibanov <83034617+alexeyshibanov@users.noreply.github.com> Date: Sat, 13 Dec 2025 18:38:47 +0300 Subject: [PATCH 3/8] feat: Enhance LogChangesOrderChangedEventHandler for improved logging and address handling --- .../LogChangesOrderChangedEventHandler.cs | 233 +++++++++--------- 1 file changed, 120 insertions(+), 113 deletions(-) diff --git a/src/VirtoCommerce.OrdersModule.Data/Handlers/LogChangesOrderChangedEventHandler.cs b/src/VirtoCommerce.OrdersModule.Data/Handlers/LogChangesOrderChangedEventHandler.cs index 44c22be3d..0a8f42767 100644 --- a/src/VirtoCommerce.OrdersModule.Data/Handlers/LogChangesOrderChangedEventHandler.cs +++ b/src/VirtoCommerce.OrdersModule.Data/Handlers/LogChangesOrderChangedEventHandler.cs @@ -25,7 +25,11 @@ public class LogChangesOrderChangedEventHandler : IEventHandler> _auditablePropertiesListByTypeDict = new(); + private static readonly ConcurrentDictionary> _auditablePropertiesCacheByTypeDict = new(); + private static readonly PropertyInfo[] _addressProperties = typeof(Address) + .GetProperties(BindingFlags.Instance | BindingFlags.Public) + .OrderBy(p => p.Name) + .ToArray(); public LogChangesOrderChangedEventHandler(IChangeLogService changeLogService, IMemberService memberService, ISettingsManager settingsManager) { @@ -40,118 +44,133 @@ public virtual async Task Handle(OrderChangedEvent message) if (logOrderChangesEnabled && message.ChangedEntries.Any()) { - var operationLogs = new List(); + var operationLogs = GetOperationLogs(message.ChangedEntries); - foreach (var changedEntry in message.ChangedEntries) + if (!operationLogs.IsNullOrEmpty()) { - if (changedEntry.EntryState == EntryState.Modified) + BackgroundJob.Enqueue(() => TryToLogChangesBackgroundJob(operationLogs.ToArray())); + } + } + } + + protected virtual List GetOperationLogs(IEnumerable> changedEntries) + { + var operationLogs = new List(); + + foreach (var changedEntry in changedEntries) + { + switch (changedEntry.EntryState) + { + case EntryState.Modified: { var originalOperations = changedEntry.OldEntry.GetFlatObjectsListWithInterface().Distinct().ToList(); var modifiedOperations = changedEntry.NewEntry.GetFlatObjectsListWithInterface().Distinct().ToList(); modifiedOperations.CompareTo(originalOperations, EqualityComparer.Default, - (state, modified, original) => operationLogs.AddRange(GetChangedEntryOperationLogs(new GenericChangedEntry(modified, original, state)))); + (state, modified, original) => operationLogs.AddRange(GetChangedEntryOperationLogs(new GenericChangedEntry(modified, original, state)))); + break; } - else if (changedEntry.EntryState == EntryState.Added || changedEntry.EntryState == EntryState.Deleted) - { + case EntryState.Added or EntryState.Deleted: operationLogs.AddRange(GetChangedEntryOperationLogs(new GenericChangedEntry(changedEntry.NewEntry, changedEntry.OldEntry, changedEntry.EntryState))); - } - } - - if (!operationLogs.IsNullOrEmpty()) - { - BackgroundJob.Enqueue(() => TryToLogChangesBackgroundJob(operationLogs.ToArray())); + break; } } + + return operationLogs; } - // (!) Do not make this method async, it causes improper user recorded into the log! It happens because the user stored in the current thread. If the thread switched, the user info will lost. - public void TryToLogChangesBackgroundJob(OperationLog[] operationLogs) + public Task TryToLogChangesBackgroundJob(OperationLog[] operationLogs) { - _changeLogService.SaveChangesAsync(operationLogs).GetAwaiter().GetResult(); + return _changeLogService.SaveChangesAsync(operationLogs); } protected virtual IEnumerable GetChangedEntryOperationLogs(GenericChangedEntry changedEntry) { var result = new List(); - if (changedEntry.EntryState == EntryState.Modified) + switch (changedEntry.EntryState) { - var logs = new List(); - - var diff = GetOperationDifferences(changedEntry, logs); + case EntryState.Modified: + { + var logs = new List(); + var diff = GetOperationDifferences(changedEntry, logs); + var auditableProperties = GetAuditableProperties(changedEntry); - var auditableProperties = GetAuditableProperties(changedEntry); + if (auditableProperties.Count > 0) + { + var observedDifferences = diff + .Where(x => auditableProperties.Contains(x.Name)) + .Distinct(); + + foreach (var difference in observedDifferences) + { + logs.Add($"The {changedEntry.OldEntry.OperationType} {changedEntry.NewEntry.Number} property '{difference.Name}' changed from '{difference.OldValue}' to '{difference.NewValue}'"); + } + } - if (auditableProperties.Count != 0) - { - var observedDifferences = diff.Join(auditableProperties, x => x.Name.ToLowerInvariant(), x => x.ToLowerInvariant(), (x, y) => x).ToArray(); - foreach (var difference in observedDifferences.Distinct(new DifferenceComparer())) + foreach (var log in logs) { - logs.Add($"The {changedEntry.OldEntry.OperationType} {changedEntry.NewEntry.Number} property '{difference.Name}' changed from '{difference.OldValue}' to '{difference.NewValue}'"); + result.Add(GetLogRecord(changedEntry.NewEntry, log)); } - } - result.AddRange(logs.Select(x => GetLogRecord(changedEntry.NewEntry, x))); - } - else if (changedEntry.EntryState == EntryState.Deleted) - { - var record = GetLogRecord(changedEntry.NewEntry, - $"The {changedEntry.NewEntry.OperationType} {changedEntry.NewEntry.Number} deleted", - EntryState.Deleted); - result.Add(record); - } - else if (changedEntry.EntryState == EntryState.Added) - { - var record = GetLogRecord(changedEntry.NewEntry, - $"The new {changedEntry.NewEntry.OperationType} {changedEntry.NewEntry.Number} added", - EntryState.Added); - result.Add(record); + break; + } + case EntryState.Deleted: + { + var record = GetLogRecord(changedEntry.NewEntry, + $"The {changedEntry.NewEntry.OperationType} {changedEntry.NewEntry.Number} deleted", + EntryState.Deleted); + result.Add(record); + break; + } + case EntryState.Added: + { + var record = GetLogRecord(changedEntry.NewEntry, + $"The new {changedEntry.NewEntry.OperationType} {changedEntry.NewEntry.Number} added", + EntryState.Added); + result.Add(record); + break; + } } return result; } - protected List GetAuditableProperties(GenericChangedEntry changedEntry) + protected static HashSet GetAuditableProperties(GenericChangedEntry changedEntry) { var type = changedEntry.OldEntry.GetType(); - if (!_auditablePropertiesListByTypeDict.TryGetValue(type.Name, out var auditableProperties)) - { - auditableProperties = type.GetProperties().Where(prop => Attribute.IsDefined(prop, typeof(AuditableAttribute))) - .Select(x => x.Name) - .ToList(); - _auditablePropertiesListByTypeDict[type.Name] = auditableProperties; - } - return auditableProperties; + return _auditablePropertiesCacheByTypeDict.GetOrAdd(type, t => new HashSet( + t.GetProperties() + .Where(prop => Attribute.IsDefined(prop, typeof(AuditableAttribute))) + .Select(x => x.Name), + StringComparer.OrdinalIgnoreCase)); } protected virtual IList GetOperationDifferences(GenericChangedEntry changedEntry, List logs) { var diff = Comparer.Compare(changedEntry.OldEntry, changedEntry.NewEntry); - if (changedEntry.OldEntry is Shipment shipment) + switch (changedEntry.OldEntry) { - logs.AddRange(GetShipmentChanges(shipment, changedEntry.NewEntry as Shipment)); - diff.AddRange(Comparer.Compare(shipment, changedEntry.NewEntry as Shipment)); - } - else if (changedEntry.OldEntry is PaymentIn payment) - { - logs.AddRange(GetPaymentChanges(payment, changedEntry.NewEntry as PaymentIn)); - diff.AddRange(Comparer.Compare(payment, changedEntry.NewEntry as PaymentIn)); - } - else if (changedEntry.OldEntry is CustomerOrder order) - { - logs.AddRange(GetCustomerOrderChanges(order, changedEntry.NewEntry as CustomerOrder)); - diff.AddRange(Comparer.Compare(order, changedEntry.NewEntry as CustomerOrder)); - } - else if (changedEntry.OldEntry is Capture capture) - { - diff.AddRange(Comparer.Compare(capture, changedEntry.NewEntry as Capture)); - } - else if (changedEntry.OldEntry is Refund refund) - { - diff.AddRange(Comparer.Compare(refund, changedEntry.NewEntry as Refund)); + case Shipment shipment: + logs.AddRange(GetShipmentChanges(shipment, changedEntry.NewEntry as Shipment)); + diff.AddRange(Comparer.Compare(shipment, changedEntry.NewEntry as Shipment)); + break; + case PaymentIn payment: + logs.AddRange(GetPaymentChanges(payment, changedEntry.NewEntry as PaymentIn)); + diff.AddRange(Comparer.Compare(payment, changedEntry.NewEntry as PaymentIn)); + break; + case CustomerOrder order: + logs.AddRange(GetCustomerOrderChanges(order, changedEntry.NewEntry as CustomerOrder)); + diff.AddRange(Comparer.Compare(order, changedEntry.NewEntry as CustomerOrder)); + break; + case Capture capture: + diff.AddRange(Comparer.Compare(capture, changedEntry.NewEntry as Capture)); + break; + case Refund refund: + diff.AddRange(Comparer.Compare(refund, changedEntry.NewEntry as Refund)); + break; } return diff; @@ -166,57 +185,59 @@ protected virtual IEnumerable GetCustomerOrderChanges(CustomerOrder orig if (!string.IsNullOrEmpty(modifiedOrder.EmployeeId)) { var employee = _memberService.GetByIdAsync(modifiedOrder.EmployeeId).GetAwaiter().GetResult() as Employee; - employeeName = employee != null ? employee.FullName : employeeName; + employeeName = employee?.FullName ?? employeeName; } result.Add($"Order employee was changed to '{employeeName}'"); } result.AddRange(GetAddressChanges(originalOrder, originalOrder.Addresses, modifiedOrder.Addresses)); - return result.ToArray(); + + return result; } protected virtual IEnumerable GetShipmentChanges(Shipment originalShipment, Shipment modifiedShipment) { - var retVal = new List(); - retVal.AddRange(GetAddressChanges(originalShipment, new[] { originalShipment.DeliveryAddress }, new[] { modifiedShipment.DeliveryAddress })); - return retVal.ToArray(); + var result = new List(); + result.AddRange(GetAddressChanges(originalShipment, [originalShipment.DeliveryAddress], [modifiedShipment.DeliveryAddress])); + + return result; } protected virtual IEnumerable GetPaymentChanges(PaymentIn payment, PaymentIn modifiedPayment) { var result = new List(); - result.AddRange(GetAddressChanges(payment, new[] { payment.BillingAddress }, new[] { modifiedPayment.BillingAddress })); + result.AddRange(GetAddressChanges(payment, [payment.BillingAddress], [modifiedPayment.BillingAddress])); + return result; } protected virtual IEnumerable GetAddressChanges(IOperation operation, IEnumerable
originalAddress, IEnumerable
modifiedAddress) { var result = new List(); - modifiedAddress.Where(x => x != null).ToList().CompareTo(originalAddress.Where(x => x != null).ToList(), EqualityComparer
.Default, - (state, source, target) => - { - if (state == EntryState.Added) - { - result.Add($"The address '{StringifyAddress(target)}' for {operation.OperationType} {operation.Number} added"); - } - else if (state == EntryState.Deleted) - { - result.Add($"The address '{StringifyAddress(target)}' for {operation.OperationType} {operation.Number} deleted"); - } - }); + + var modifiedAddressList = modifiedAddress?.Where(x => x != null).ToList() ?? []; + var originalAddressList = originalAddress?.Where(x => x != null).ToList() ?? []; + + modifiedAddressList.CompareTo(originalAddressList, EqualityComparer
.Default, (state, source, target) => + { + switch (state) + { + case EntryState.Added: + result.Add($"The address '{StringifyAddress(target)}' for {operation.OperationType} {operation.Number} added"); + break; + case EntryState.Deleted: + result.Add($"The address '{StringifyAddress(target)}' for {operation.OperationType} {operation.Number} deleted"); + break; + } + }); + return result; } protected virtual string StringifyAddress(Address address) { - var result = ""; - if (address != null) - { - return string.Join(", ", typeof(Address).GetProperties(BindingFlags.Instance | BindingFlags.Public) - .OrderBy(p => p.Name) - .Select(p => p.GetValue(address)) - .Where(x => x != null)); - } - return result; + return address is null + ? string.Empty + : string.Join(", ", _addressProperties.Select(p => p.GetValue(address)).Where(x => x != null)); } protected virtual OperationLog GetLogRecord(IOperation operation, string template, EntryState operationType = EntryState.Modified) @@ -231,18 +252,4 @@ protected virtual OperationLog GetLogRecord(IOperation operation, string templat return result; } } - - internal class DifferenceComparer : EqualityComparer - { - public override bool Equals(Difference x, Difference y) - { - return GetHashCode(x) == GetHashCode(y); - } - - public override int GetHashCode(Difference obj) - { - var result = string.Join(":", obj.Name, obj.NewValue, obj.OldValue); - return result.GetHashCode(); - } - } } From 2507253aebc77e086659af6eb1dd603da329cd8e Mon Sep 17 00:00:00 2001 From: Alexey Shibanov <83034617+alexeyshibanov@users.noreply.github.com> Date: Sat, 13 Dec 2025 20:12:44 +0300 Subject: [PATCH 4/8] feat: Update ModuleConstants to use modern syntax for improved clarity and consistency --- .../ModuleConstants.cs | 69 ++++++++++--------- 1 file changed, 35 insertions(+), 34 deletions(-) diff --git a/src/VirtoCommerce.OrdersModule.Core/ModuleConstants.cs b/src/VirtoCommerce.OrdersModule.Core/ModuleConstants.cs index ce0a17d2f..545c576a9 100644 --- a/src/VirtoCommerce.OrdersModule.Core/ModuleConstants.cs +++ b/src/VirtoCommerce.OrdersModule.Core/ModuleConstants.cs @@ -30,7 +30,8 @@ public static class Permissions public const string RefundPayment = "order:refund"; public const string ViewDashboardStatistics = "order:dashboardstatistics:view"; - public static string[] AllPermissions { get; } = { + public static string[] AllPermissions { get; } = + [ Read, Create, Update, @@ -41,7 +42,7 @@ public static class Permissions CapturePayment, RefundPayment, ViewDashboardStatistics, - }; + ]; } } @@ -61,15 +62,15 @@ public static class Settings { public static class General { - public static SettingDescriptor OrderStatus = new SettingDescriptor + public static SettingDescriptor OrderStatus { get; } = new() { Name = "Order.Status", ValueType = SettingValueType.ShortText, GroupName = "Orders|General", IsDictionary = true, IsLocalizable = true, - AllowedValues = new object[] - { + AllowedValues = + [ CustomerOrderStatus.New, CustomerOrderStatus.NotPayed, CustomerOrderStatus.Pending, @@ -78,7 +79,7 @@ public static class General CustomerOrderStatus.Cancelled, CustomerOrderStatus.PartiallySent, CustomerOrderStatus.Completed, - } + ], }; public static SettingDescriptor OrderInitialStatus { get; } = new() @@ -104,7 +105,7 @@ public static class General GroupName = "Orders|General", IsDictionary = true, IsLocalizable = true, - AllowedValues = new object[] { "Pending", "InProgress", "Shipped", "Delivered", "Cancelled" }, + AllowedValues = ["Pending", "InProgress", "Shipped", "Delivered", "Cancelled"], }; public static SettingDescriptor OrderLineItemInitialStatus { get; } = new() @@ -114,7 +115,7 @@ public static class General GroupName = "Orders|General", }; - public static SettingDescriptor ShipmentStatus = new SettingDescriptor + public static SettingDescriptor ShipmentStatus { get; } = new() { Name = "Shipment.Status", ValueType = SettingValueType.ShortText, @@ -122,10 +123,10 @@ public static class General IsDictionary = true, IsLocalizable = true, DefaultValue = "New", - AllowedValues = new object[] { "New", "PickPack", "Cancelled", "ReadyToSend", "Sent" } + AllowedValues = ["New", "PickPack", "Cancelled", "ReadyToSend", "Sent"], }; - public static SettingDescriptor PaymentInStatus = new SettingDescriptor + public static SettingDescriptor PaymentInStatus { get; } = new() { Name = "PaymentIn.Status", ValueType = SettingValueType.ShortText, @@ -133,10 +134,10 @@ public static class General IsDictionary = true, IsLocalizable = true, DefaultValue = "New", - AllowedValues = new object[] { "New", "Pending", "Authorized", "Paid", "PartiallyRefunded", "Refunded", "Voided", "Custom", "Cancelled" } + AllowedValues = ["New", "Pending", "Authorized", "Paid", "PartiallyRefunded", "Refunded", "Voided", "Custom", "Cancelled"], }; - public static SettingDescriptor RefundStatus { get; } = new SettingDescriptor + public static SettingDescriptor RefundStatus { get; } = new() { Name = "Refund.Status", ValueType = SettingValueType.ShortText, @@ -144,50 +145,50 @@ public static class General IsDictionary = true, IsLocalizable = true, DefaultValue = "Pending", - AllowedValues = new object[] { "Pending", "Rejected", "Processed" } + AllowedValues = ["Pending", "Rejected", "Processed"], }; - public static SettingDescriptor OrderCustomerOrderNewNumberTemplate = new SettingDescriptor + public static SettingDescriptor OrderCustomerOrderNewNumberTemplate { get; } = new() { Name = "Order.CustomerOrderNewNumberTemplate", ValueType = SettingValueType.ShortText, GroupName = "Orders|Orders", - DefaultValue = "CO{0:yyMMdd}-{1:D5}" + DefaultValue = "CO{0:yyMMdd}-{1:D5}", }; - public static SettingDescriptor OrderShipmentNewNumberTemplate = new SettingDescriptor + public static SettingDescriptor OrderShipmentNewNumberTemplate { get; } = new() { Name = "Order.ShipmentNewNumberTemplate", ValueType = SettingValueType.ShortText, GroupName = "Orders|Orders", - DefaultValue = "SH{0:yyMMdd}-{1:D5}" + DefaultValue = "SH{0:yyMMdd}-{1:D5}", }; - public static SettingDescriptor OrderPaymentInNewNumberTemplate = new SettingDescriptor + public static SettingDescriptor OrderPaymentInNewNumberTemplate { get; } = new() { Name = "Order.PaymentInNewNumberTemplate", ValueType = SettingValueType.ShortText, GroupName = "Orders|Orders", - DefaultValue = "PI{0:yyMMdd}-{1:D5}" + DefaultValue = "PI{0:yyMMdd}-{1:D5}", }; - public static SettingDescriptor RefundNewNumberTemplate { get; } = new SettingDescriptor + public static SettingDescriptor RefundNewNumberTemplate { get; } = new() { Name = "Order.RefundNewNumberTemplate", ValueType = SettingValueType.ShortText, GroupName = "Orders|Orders", - DefaultValue = "RE{0:yyMMdd}-{1:D5}" + DefaultValue = "RE{0:yyMMdd}-{1:D5}", }; - public static SettingDescriptor CaptureNewNumberTemplate { get; } = new SettingDescriptor + public static SettingDescriptor CaptureNewNumberTemplate { get; } = new() { Name = "Order.CaptureNewNumberTemplate", ValueType = SettingValueType.ShortText, GroupName = "Orders|Orders", - DefaultValue = "CA{0:yyMMdd}-{1:D5}" + DefaultValue = "CA{0:yyMMdd}-{1:D5}", }; - public static SettingDescriptor SendOrderNotifications = new SettingDescriptor + public static SettingDescriptor SendOrderNotifications { get; } = new() { Name = "Order.SendOrderNotifications", GroupName = "Orders|General", @@ -195,7 +196,7 @@ public static class General DefaultValue = true, }; - public static SettingDescriptor OrderAdjustInventory = new SettingDescriptor + public static SettingDescriptor OrderAdjustInventory { get; } = new() { Name = "Order.AdjustInventory", GroupName = "Orders|General", @@ -203,7 +204,7 @@ public static class General DefaultValue = true, }; - public static SettingDescriptor LogOrderChanges { get; } = new SettingDescriptor + public static SettingDescriptor LogOrderChanges { get; } = new() { Name = "Order.LogOrderChanges", GroupName = "Orders|General", @@ -211,7 +212,7 @@ public static class General DefaultValue = true, }; - public static SettingDescriptor EventBasedIndexation { get; } = new SettingDescriptor + public static SettingDescriptor EventBasedIndexation { get; } = new() { Name = "Order.Search.EventBasedIndexation.Enable", GroupName = "Orders|General", @@ -219,7 +220,7 @@ public static class General DefaultValue = true, }; - public static SettingDescriptor CustomerOrderIndexationDate { get; } = new SettingDescriptor + public static SettingDescriptor CustomerOrderIndexationDate { get; } = new() { Name = "VirtoCommerce.Search.IndexingJobs.IndexationDate.CustomerOrder", GroupName = "Orders|General", @@ -227,7 +228,7 @@ public static class General DefaultValue = default(DateTime), }; - public static SettingDescriptor CustomerOrderValidation { get; } = new SettingDescriptor + public static SettingDescriptor CustomerOrderValidation { get; } = new() { Name = "Order.Validation.Enable", GroupName = "Orders|General", @@ -235,7 +236,7 @@ public static class General DefaultValue = false }; - public static SettingDescriptor OrderPaidAndOrderSentNotifications { get; } = new SettingDescriptor + public static SettingDescriptor OrderPaidAndOrderSentNotifications { get; } = new() { Name = "Order.OrderPaidAndOrderSentNotifications.Enable", GroupName = "Orders|Notification", @@ -243,7 +244,7 @@ public static class General DefaultValue = true, }; - public static SettingDescriptor PaymentShipmentStatusChangedNotifications { get; } = new SettingDescriptor + public static SettingDescriptor PaymentShipmentStatusChangedNotifications { get; } = new() { Name = "Order.PaymentShipmentStatusChangedNotifications.Enable", GroupName = "Orders|Notification", @@ -251,7 +252,7 @@ public static class General DefaultValue = false, }; - public static SettingDescriptor PurchasedProductIndexation { get; } = new SettingDescriptor + public static SettingDescriptor PurchasedProductIndexation { get; } = new() { Name = "Order.PurchasedProductIndexation.Enable", GroupName = "Orders|Products", @@ -259,7 +260,7 @@ public static class General DefaultValue = false, }; - public static SettingDescriptor EventBasedPurchasedProductIndexation { get; } = new SettingDescriptor + public static SettingDescriptor EventBasedPurchasedProductIndexation { get; } = new() { Name = "Order.EventBasedPurchasedProductIndexation.Enable", GroupName = "Orders|Products", @@ -267,7 +268,7 @@ public static class General DefaultValue = true }; - public static SettingDescriptor PurchasedProductStoreFilter { get; } = new SettingDescriptor + public static SettingDescriptor PurchasedProductStoreFilter { get; } = new() { Name = "Order.PurchasedProductStoreFilter.Enable", GroupName = "Orders|Products", From ddff5d51e46e73344620332d500dbbae2fe888eb Mon Sep 17 00:00:00 2001 From: Alexey Shibanov <83034617+alexeyshibanov@users.noreply.github.com> Date: Sun, 14 Dec 2025 14:05:22 +0300 Subject: [PATCH 5/8] feat: Refactor GetOperationLogs method to return IList and implement DifferenceComparer for distinct operations --- .../LogChangesOrderChangedEventHandler.cs | 29 +++++++++++++++++-- 1 file changed, 27 insertions(+), 2 deletions(-) diff --git a/src/VirtoCommerce.OrdersModule.Data/Handlers/LogChangesOrderChangedEventHandler.cs b/src/VirtoCommerce.OrdersModule.Data/Handlers/LogChangesOrderChangedEventHandler.cs index 0a8f42767..8e4a6e77a 100644 --- a/src/VirtoCommerce.OrdersModule.Data/Handlers/LogChangesOrderChangedEventHandler.cs +++ b/src/VirtoCommerce.OrdersModule.Data/Handlers/LogChangesOrderChangedEventHandler.cs @@ -53,7 +53,7 @@ public virtual async Task Handle(OrderChangedEvent message) } } - protected virtual List GetOperationLogs(IEnumerable> changedEntries) + protected virtual IList GetOperationLogs(IEnumerable> changedEntries) { var operationLogs = new List(); @@ -100,7 +100,7 @@ protected virtual IEnumerable GetChangedEntryOperationLogs(Generic { var observedDifferences = diff .Where(x => auditableProperties.Contains(x.Name)) - .Distinct(); + .Distinct(new DifferenceComparer()); foreach (var difference in observedDifferences) { @@ -252,4 +252,29 @@ protected virtual OperationLog GetLogRecord(IOperation operation, string templat return result; } } + + internal sealed class DifferenceComparer : EqualityComparer + { + public override bool Equals(Difference x, Difference y) + { + if (ReferenceEquals(x, y)) + { + return true; + } + + if (x is null || y is null) + { + return false; + } + + return string.Equals(x.Name, y.Name, StringComparison.Ordinal) && + Equals(x.OldValue, y.OldValue) && + Equals(x.NewValue, y.NewValue); + } + + public override int GetHashCode(Difference obj) + { + return HashCode.Combine(obj.Name, obj.OldValue, obj.NewValue); + } + } } From 598451711a322c23a17d170bc6d18dfd93b5943e Mon Sep 17 00:00:00 2001 From: Alexey Shibanov <83034617+alexeyshibanov@users.noreply.github.com> Date: Sun, 14 Dec 2025 16:20:50 +0300 Subject: [PATCH 6/8] Revert "feat: Update ModuleConstants to use modern syntax for improved clarity and consistency" This reverts commit 2507253aebc77e086659af6eb1dd603da329cd8e. --- .../ModuleConstants.cs | 69 +++++++++---------- 1 file changed, 34 insertions(+), 35 deletions(-) diff --git a/src/VirtoCommerce.OrdersModule.Core/ModuleConstants.cs b/src/VirtoCommerce.OrdersModule.Core/ModuleConstants.cs index 545c576a9..ce0a17d2f 100644 --- a/src/VirtoCommerce.OrdersModule.Core/ModuleConstants.cs +++ b/src/VirtoCommerce.OrdersModule.Core/ModuleConstants.cs @@ -30,8 +30,7 @@ public static class Permissions public const string RefundPayment = "order:refund"; public const string ViewDashboardStatistics = "order:dashboardstatistics:view"; - public static string[] AllPermissions { get; } = - [ + public static string[] AllPermissions { get; } = { Read, Create, Update, @@ -42,7 +41,7 @@ public static class Permissions CapturePayment, RefundPayment, ViewDashboardStatistics, - ]; + }; } } @@ -62,15 +61,15 @@ public static class Settings { public static class General { - public static SettingDescriptor OrderStatus { get; } = new() + public static SettingDescriptor OrderStatus = new SettingDescriptor { Name = "Order.Status", ValueType = SettingValueType.ShortText, GroupName = "Orders|General", IsDictionary = true, IsLocalizable = true, - AllowedValues = - [ + AllowedValues = new object[] + { CustomerOrderStatus.New, CustomerOrderStatus.NotPayed, CustomerOrderStatus.Pending, @@ -79,7 +78,7 @@ public static class General CustomerOrderStatus.Cancelled, CustomerOrderStatus.PartiallySent, CustomerOrderStatus.Completed, - ], + } }; public static SettingDescriptor OrderInitialStatus { get; } = new() @@ -105,7 +104,7 @@ public static class General GroupName = "Orders|General", IsDictionary = true, IsLocalizable = true, - AllowedValues = ["Pending", "InProgress", "Shipped", "Delivered", "Cancelled"], + AllowedValues = new object[] { "Pending", "InProgress", "Shipped", "Delivered", "Cancelled" }, }; public static SettingDescriptor OrderLineItemInitialStatus { get; } = new() @@ -115,7 +114,7 @@ public static class General GroupName = "Orders|General", }; - public static SettingDescriptor ShipmentStatus { get; } = new() + public static SettingDescriptor ShipmentStatus = new SettingDescriptor { Name = "Shipment.Status", ValueType = SettingValueType.ShortText, @@ -123,10 +122,10 @@ public static class General IsDictionary = true, IsLocalizable = true, DefaultValue = "New", - AllowedValues = ["New", "PickPack", "Cancelled", "ReadyToSend", "Sent"], + AllowedValues = new object[] { "New", "PickPack", "Cancelled", "ReadyToSend", "Sent" } }; - public static SettingDescriptor PaymentInStatus { get; } = new() + public static SettingDescriptor PaymentInStatus = new SettingDescriptor { Name = "PaymentIn.Status", ValueType = SettingValueType.ShortText, @@ -134,10 +133,10 @@ public static class General IsDictionary = true, IsLocalizable = true, DefaultValue = "New", - AllowedValues = ["New", "Pending", "Authorized", "Paid", "PartiallyRefunded", "Refunded", "Voided", "Custom", "Cancelled"], + AllowedValues = new object[] { "New", "Pending", "Authorized", "Paid", "PartiallyRefunded", "Refunded", "Voided", "Custom", "Cancelled" } }; - public static SettingDescriptor RefundStatus { get; } = new() + public static SettingDescriptor RefundStatus { get; } = new SettingDescriptor { Name = "Refund.Status", ValueType = SettingValueType.ShortText, @@ -145,50 +144,50 @@ public static class General IsDictionary = true, IsLocalizable = true, DefaultValue = "Pending", - AllowedValues = ["Pending", "Rejected", "Processed"], + AllowedValues = new object[] { "Pending", "Rejected", "Processed" } }; - public static SettingDescriptor OrderCustomerOrderNewNumberTemplate { get; } = new() + public static SettingDescriptor OrderCustomerOrderNewNumberTemplate = new SettingDescriptor { Name = "Order.CustomerOrderNewNumberTemplate", ValueType = SettingValueType.ShortText, GroupName = "Orders|Orders", - DefaultValue = "CO{0:yyMMdd}-{1:D5}", + DefaultValue = "CO{0:yyMMdd}-{1:D5}" }; - public static SettingDescriptor OrderShipmentNewNumberTemplate { get; } = new() + public static SettingDescriptor OrderShipmentNewNumberTemplate = new SettingDescriptor { Name = "Order.ShipmentNewNumberTemplate", ValueType = SettingValueType.ShortText, GroupName = "Orders|Orders", - DefaultValue = "SH{0:yyMMdd}-{1:D5}", + DefaultValue = "SH{0:yyMMdd}-{1:D5}" }; - public static SettingDescriptor OrderPaymentInNewNumberTemplate { get; } = new() + public static SettingDescriptor OrderPaymentInNewNumberTemplate = new SettingDescriptor { Name = "Order.PaymentInNewNumberTemplate", ValueType = SettingValueType.ShortText, GroupName = "Orders|Orders", - DefaultValue = "PI{0:yyMMdd}-{1:D5}", + DefaultValue = "PI{0:yyMMdd}-{1:D5}" }; - public static SettingDescriptor RefundNewNumberTemplate { get; } = new() + public static SettingDescriptor RefundNewNumberTemplate { get; } = new SettingDescriptor { Name = "Order.RefundNewNumberTemplate", ValueType = SettingValueType.ShortText, GroupName = "Orders|Orders", - DefaultValue = "RE{0:yyMMdd}-{1:D5}", + DefaultValue = "RE{0:yyMMdd}-{1:D5}" }; - public static SettingDescriptor CaptureNewNumberTemplate { get; } = new() + public static SettingDescriptor CaptureNewNumberTemplate { get; } = new SettingDescriptor { Name = "Order.CaptureNewNumberTemplate", ValueType = SettingValueType.ShortText, GroupName = "Orders|Orders", - DefaultValue = "CA{0:yyMMdd}-{1:D5}", + DefaultValue = "CA{0:yyMMdd}-{1:D5}" }; - public static SettingDescriptor SendOrderNotifications { get; } = new() + public static SettingDescriptor SendOrderNotifications = new SettingDescriptor { Name = "Order.SendOrderNotifications", GroupName = "Orders|General", @@ -196,7 +195,7 @@ public static class General DefaultValue = true, }; - public static SettingDescriptor OrderAdjustInventory { get; } = new() + public static SettingDescriptor OrderAdjustInventory = new SettingDescriptor { Name = "Order.AdjustInventory", GroupName = "Orders|General", @@ -204,7 +203,7 @@ public static class General DefaultValue = true, }; - public static SettingDescriptor LogOrderChanges { get; } = new() + public static SettingDescriptor LogOrderChanges { get; } = new SettingDescriptor { Name = "Order.LogOrderChanges", GroupName = "Orders|General", @@ -212,7 +211,7 @@ public static class General DefaultValue = true, }; - public static SettingDescriptor EventBasedIndexation { get; } = new() + public static SettingDescriptor EventBasedIndexation { get; } = new SettingDescriptor { Name = "Order.Search.EventBasedIndexation.Enable", GroupName = "Orders|General", @@ -220,7 +219,7 @@ public static class General DefaultValue = true, }; - public static SettingDescriptor CustomerOrderIndexationDate { get; } = new() + public static SettingDescriptor CustomerOrderIndexationDate { get; } = new SettingDescriptor { Name = "VirtoCommerce.Search.IndexingJobs.IndexationDate.CustomerOrder", GroupName = "Orders|General", @@ -228,7 +227,7 @@ public static class General DefaultValue = default(DateTime), }; - public static SettingDescriptor CustomerOrderValidation { get; } = new() + public static SettingDescriptor CustomerOrderValidation { get; } = new SettingDescriptor { Name = "Order.Validation.Enable", GroupName = "Orders|General", @@ -236,7 +235,7 @@ public static class General DefaultValue = false }; - public static SettingDescriptor OrderPaidAndOrderSentNotifications { get; } = new() + public static SettingDescriptor OrderPaidAndOrderSentNotifications { get; } = new SettingDescriptor { Name = "Order.OrderPaidAndOrderSentNotifications.Enable", GroupName = "Orders|Notification", @@ -244,7 +243,7 @@ public static class General DefaultValue = true, }; - public static SettingDescriptor PaymentShipmentStatusChangedNotifications { get; } = new() + public static SettingDescriptor PaymentShipmentStatusChangedNotifications { get; } = new SettingDescriptor { Name = "Order.PaymentShipmentStatusChangedNotifications.Enable", GroupName = "Orders|Notification", @@ -252,7 +251,7 @@ public static class General DefaultValue = false, }; - public static SettingDescriptor PurchasedProductIndexation { get; } = new() + public static SettingDescriptor PurchasedProductIndexation { get; } = new SettingDescriptor { Name = "Order.PurchasedProductIndexation.Enable", GroupName = "Orders|Products", @@ -260,7 +259,7 @@ public static class General DefaultValue = false, }; - public static SettingDescriptor EventBasedPurchasedProductIndexation { get; } = new() + public static SettingDescriptor EventBasedPurchasedProductIndexation { get; } = new SettingDescriptor { Name = "Order.EventBasedPurchasedProductIndexation.Enable", GroupName = "Orders|Products", @@ -268,7 +267,7 @@ public static class General DefaultValue = true }; - public static SettingDescriptor PurchasedProductStoreFilter { get; } = new() + public static SettingDescriptor PurchasedProductStoreFilter { get; } = new SettingDescriptor { Name = "Order.PurchasedProductStoreFilter.Enable", GroupName = "Orders|Products", From 4facbcd0db02c5324cc5bf08a0c8a8f6e9ff7ec2 Mon Sep 17 00:00:00 2001 From: Artem Dudarev Date: Thu, 18 Dec 2025 18:29:36 +0200 Subject: [PATCH 7/8] Refactoring --- VirtoCommerce.OrdersModule.sln.DotSettings | 3 +- .../Model/Address.cs | 7 +- .../IndexCustomerOrderChangedEventHandler.cs | 39 +++---- .../LogChangesOrderChangedEventHandler.cs | 109 ++++++++---------- .../Indexed/CustomerOrderChangesProvider.cs | 29 +++-- 5 files changed, 89 insertions(+), 98 deletions(-) diff --git a/VirtoCommerce.OrdersModule.sln.DotSettings b/VirtoCommerce.OrdersModule.sln.DotSettings index c15dfd8b0..5ab6883d0 100644 --- a/VirtoCommerce.OrdersModule.sln.DotSettings +++ b/VirtoCommerce.OrdersModule.sln.DotSettings @@ -1,4 +1,5 @@ - + + <Policy><Descriptor Staticness="Static" AccessRightKinds="Private" Description="Static readonly fields (private)"><ElementKinds><Kind Name="READONLY_FIELD" /></ElementKinds></Descriptor><Policy Inspect="True" WarnAboutPrefixesAndSuffixes="False" Prefix="_" Suffix="" Style="aaBb" /></Policy> True True True diff --git a/src/VirtoCommerce.OrdersModule.Core/Model/Address.cs b/src/VirtoCommerce.OrdersModule.Core/Model/Address.cs index b84d1a3ee..884362f4b 100644 --- a/src/VirtoCommerce.OrdersModule.Core/Model/Address.cs +++ b/src/VirtoCommerce.OrdersModule.Core/Model/Address.cs @@ -1,3 +1,5 @@ +using System.Collections.Generic; +using System.Reflection; using VirtoCommerce.Platform.Core.Swagger; namespace VirtoCommerce.OrdersModule.Core.Model @@ -5,6 +7,9 @@ namespace VirtoCommerce.OrdersModule.Core.Model [SwaggerSchemaId("OrderAddress")] public class Address : CoreModule.Core.Common.Address { + public virtual IEnumerable GetAllProperties() + { + return GetProperties(); + } } - } diff --git a/src/VirtoCommerce.OrdersModule.Data/Handlers/IndexCustomerOrderChangedEventHandler.cs b/src/VirtoCommerce.OrdersModule.Data/Handlers/IndexCustomerOrderChangedEventHandler.cs index 66285494f..2e200af24 100644 --- a/src/VirtoCommerce.OrdersModule.Data/Handlers/IndexCustomerOrderChangedEventHandler.cs +++ b/src/VirtoCommerce.OrdersModule.Data/Handlers/IndexCustomerOrderChangedEventHandler.cs @@ -17,13 +17,10 @@ namespace VirtoCommerce.OrdersModule.Data.Handlers { public class IndexCustomerOrderChangedEventHandler : IEventHandler { - protected ISettingsManager SettingsManager { get; } - - protected IConfiguration Configuration { get; } - - protected IIndexingJobService IndexingJobService { get; } - - protected IEnumerable IndexingConfigurations { get; } + private readonly ISettingsManager _settingsManager; + private readonly IConfiguration _configuration; + private readonly IIndexingJobService _indexingJobService; + private readonly IEnumerable _indexingConfigurations; public IndexCustomerOrderChangedEventHandler( ISettingsManager settingsManager, @@ -31,45 +28,43 @@ public IndexCustomerOrderChangedEventHandler( IIndexingJobService indexingJobService, IEnumerable indexingConfigurations) { - SettingsManager = settingsManager; - Configuration = configuration; - IndexingJobService = indexingJobService; - IndexingConfigurations = indexingConfigurations; + _settingsManager = settingsManager; + _configuration = configuration; + _indexingJobService = indexingJobService; + _indexingConfigurations = indexingConfigurations; } public virtual async Task Handle(OrderChangedEvent message) { - if (!await ShouldIndexAsync()) + if (await ShouldIndexAsync()) { - return; + await IndexOrdersAsync(message); } - - await IndexOrdersAsync(message); } protected virtual async Task ShouldIndexAsync() { - return Configuration.IsOrderFullTextSearchEnabled() && - await SettingsManager.GetValueAsync(ModuleConstants.Settings.General.EventBasedIndexation); + return _configuration.IsOrderFullTextSearchEnabled() && + await _settingsManager.GetValueAsync(ModuleConstants.Settings.General.EventBasedIndexation); } protected virtual Task IndexOrdersAsync(OrderChangedEvent message) { var indexEntries = GetOrderIndexEntries(message); - if (indexEntries.Length > 0) + if (indexEntries.Count > 0) { - var documentBuilders = IndexingConfigurations + var documentBuilders = _indexingConfigurations .GetDocumentBuilders(ModuleConstants.OrderIndexDocumentType, typeof(CustomerOrderChangesProvider)) .ToList(); - IndexingJobService.EnqueueIndexAndDeleteDocuments(indexEntries, JobPriority.Normal, documentBuilders); + _indexingJobService.EnqueueIndexAndDeleteDocuments(indexEntries, JobPriority.Normal, documentBuilders); } return Task.CompletedTask; } - protected virtual IndexEntry[] GetOrderIndexEntries(OrderChangedEvent message) + protected virtual IList GetOrderIndexEntries(OrderChangedEvent message) { return message?.ChangedEntries .Select(x => new IndexEntry @@ -78,7 +73,7 @@ protected virtual IndexEntry[] GetOrderIndexEntries(OrderChangedEvent message) EntryState = x.EntryState, Type = ModuleConstants.OrderIndexDocumentType, }) - .ToArray() ?? []; + .ToList() ?? []; } } } diff --git a/src/VirtoCommerce.OrdersModule.Data/Handlers/LogChangesOrderChangedEventHandler.cs b/src/VirtoCommerce.OrdersModule.Data/Handlers/LogChangesOrderChangedEventHandler.cs index 8e4a6e77a..43c361431 100644 --- a/src/VirtoCommerce.OrdersModule.Data/Handlers/LogChangesOrderChangedEventHandler.cs +++ b/src/VirtoCommerce.OrdersModule.Data/Handlers/LogChangesOrderChangedEventHandler.cs @@ -2,7 +2,6 @@ using System.Collections.Concurrent; using System.Collections.Generic; using System.Linq; -using System.Reflection; using System.Threading.Tasks; using AutoCompare; using Hangfire; @@ -25,11 +24,7 @@ public class LogChangesOrderChangedEventHandler : IEventHandler> _auditablePropertiesCacheByTypeDict = new(); - private static readonly PropertyInfo[] _addressProperties = typeof(Address) - .GetProperties(BindingFlags.Instance | BindingFlags.Public) - .OrderBy(p => p.Name) - .ToArray(); + private static readonly ConcurrentDictionary> _auditablePropertiesCacheByTypeDict = new(); public LogChangesOrderChangedEventHandler(IChangeLogService changeLogService, IMemberService memberService, ISettingsManager settingsManager) { @@ -62,14 +57,14 @@ protected virtual IList GetOperationLogs(IEnumerable().Distinct().ToList(); - var modifiedOperations = changedEntry.NewEntry.GetFlatObjectsListWithInterface().Distinct().ToList(); + { + var originalOperations = changedEntry.OldEntry.GetFlatObjectsListWithInterface().Distinct().ToList(); + var modifiedOperations = changedEntry.NewEntry.GetFlatObjectsListWithInterface().Distinct().ToList(); - modifiedOperations.CompareTo(originalOperations, EqualityComparer.Default, - (state, modified, original) => operationLogs.AddRange(GetChangedEntryOperationLogs(new GenericChangedEntry(modified, original, state)))); - break; - } + modifiedOperations.CompareTo(originalOperations, EqualityComparer.Default, + (state, modified, original) => operationLogs.AddRange(GetChangedEntryOperationLogs(new GenericChangedEntry(modified, original, state)))); + break; + } case EntryState.Added or EntryState.Deleted: operationLogs.AddRange(GetChangedEntryOperationLogs(new GenericChangedEntry(changedEntry.NewEntry, changedEntry.OldEntry, changedEntry.EntryState))); break; @@ -91,60 +86,60 @@ protected virtual IEnumerable GetChangedEntryOperationLogs(Generic switch (changedEntry.EntryState) { case EntryState.Modified: - { - var logs = new List(); - var diff = GetOperationDifferences(changedEntry, logs); - var auditableProperties = GetAuditableProperties(changedEntry); - - if (auditableProperties.Count > 0) { - var observedDifferences = diff - .Where(x => auditableProperties.Contains(x.Name)) - .Distinct(new DifferenceComparer()); + var logs = new List(); + var diff = GetOperationDifferences(changedEntry, logs); + var auditableProperties = GetAuditableProperties(changedEntry); - foreach (var difference in observedDifferences) + if (auditableProperties.Count > 0) { - logs.Add($"The {changedEntry.OldEntry.OperationType} {changedEntry.NewEntry.Number} property '{difference.Name}' changed from '{difference.OldValue}' to '{difference.NewValue}'"); + var observedDifferences = diff + .Where(x => auditableProperties.ContainsIgnoreCase(x.Name)) + .Distinct(new DifferenceComparer()); + + foreach (var difference in observedDifferences) + { + logs.Add($"The {changedEntry.OldEntry.OperationType} {changedEntry.NewEntry.Number} property '{difference.Name}' changed from '{difference.OldValue}' to '{difference.NewValue}'"); + } } - } - foreach (var log in logs) - { - result.Add(GetLogRecord(changedEntry.NewEntry, log)); - } + foreach (var log in logs) + { + result.Add(GetLogRecord(changedEntry.NewEntry, log)); + } - break; - } + break; + } case EntryState.Deleted: - { - var record = GetLogRecord(changedEntry.NewEntry, - $"The {changedEntry.NewEntry.OperationType} {changedEntry.NewEntry.Number} deleted", - EntryState.Deleted); - result.Add(record); - break; - } + { + var record = GetLogRecord(changedEntry.NewEntry, + $"The {changedEntry.NewEntry.OperationType} {changedEntry.NewEntry.Number} deleted", + EntryState.Deleted); + result.Add(record); + break; + } case EntryState.Added: - { - var record = GetLogRecord(changedEntry.NewEntry, - $"The new {changedEntry.NewEntry.OperationType} {changedEntry.NewEntry.Number} added", - EntryState.Added); - result.Add(record); - break; - } + { + var record = GetLogRecord(changedEntry.NewEntry, + $"The new {changedEntry.NewEntry.OperationType} {changedEntry.NewEntry.Number} added", + EntryState.Added); + result.Add(record); + break; + } } return result; } - protected static HashSet GetAuditableProperties(GenericChangedEntry changedEntry) + protected static List GetAuditableProperties(GenericChangedEntry changedEntry) { var type = changedEntry.OldEntry.GetType(); - return _auditablePropertiesCacheByTypeDict.GetOrAdd(type, t => new HashSet( + return _auditablePropertiesCacheByTypeDict.GetOrAdd(type, t => t.GetProperties() .Where(prop => Attribute.IsDefined(prop, typeof(AuditableAttribute))) - .Select(x => x.Name), - StringComparer.OrdinalIgnoreCase)); + .Select(x => x.Name) + .ToList()); } protected virtual IList GetOperationDifferences(GenericChangedEntry changedEntry, List logs) @@ -179,6 +174,7 @@ protected virtual IList GetOperationDifferences(GenericChangedEntry< protected virtual IEnumerable GetCustomerOrderChanges(CustomerOrder originalOrder, CustomerOrder modifiedOrder) { var result = new List(); + if (originalOrder.EmployeeId != modifiedOrder.EmployeeId) { var employeeName = "none"; @@ -189,6 +185,7 @@ protected virtual IEnumerable GetCustomerOrderChanges(CustomerOrder orig } result.Add($"Order employee was changed to '{employeeName}'"); } + result.AddRange(GetAddressChanges(originalOrder, originalOrder.Addresses, modifiedOrder.Addresses)); return result; @@ -196,18 +193,12 @@ protected virtual IEnumerable GetCustomerOrderChanges(CustomerOrder orig protected virtual IEnumerable GetShipmentChanges(Shipment originalShipment, Shipment modifiedShipment) { - var result = new List(); - result.AddRange(GetAddressChanges(originalShipment, [originalShipment.DeliveryAddress], [modifiedShipment.DeliveryAddress])); - - return result; + return GetAddressChanges(originalShipment, [originalShipment.DeliveryAddress], [modifiedShipment.DeliveryAddress]); } protected virtual IEnumerable GetPaymentChanges(PaymentIn payment, PaymentIn modifiedPayment) { - var result = new List(); - result.AddRange(GetAddressChanges(payment, [payment.BillingAddress], [modifiedPayment.BillingAddress])); - - return result; + return GetAddressChanges(payment, [payment.BillingAddress], [modifiedPayment.BillingAddress]); } protected virtual IEnumerable GetAddressChanges(IOperation operation, IEnumerable
originalAddress, IEnumerable
modifiedAddress) @@ -217,7 +208,7 @@ protected virtual IEnumerable GetAddressChanges(IOperation operation, IE var modifiedAddressList = modifiedAddress?.Where(x => x != null).ToList() ?? []; var originalAddressList = originalAddress?.Where(x => x != null).ToList() ?? []; - modifiedAddressList.CompareTo(originalAddressList, EqualityComparer
.Default, (state, source, target) => + modifiedAddressList.CompareTo(originalAddressList, EqualityComparer
.Default, (state, _, target) => { switch (state) { @@ -237,7 +228,7 @@ protected virtual string StringifyAddress(Address address) { return address is null ? string.Empty - : string.Join(", ", _addressProperties.Select(p => p.GetValue(address)).Where(x => x != null)); + : string.Join(", ", address.GetAllProperties().Select(p => p.GetValue(address)).Where(x => x != null)); } protected virtual OperationLog GetLogRecord(IOperation operation, string template, EntryState operationType = EntryState.Modified) diff --git a/src/VirtoCommerce.OrdersModule.Data/Search/Indexed/CustomerOrderChangesProvider.cs b/src/VirtoCommerce.OrdersModule.Data/Search/Indexed/CustomerOrderChangesProvider.cs index 0332054df..5da548680 100644 --- a/src/VirtoCommerce.OrdersModule.Data/Search/Indexed/CustomerOrderChangesProvider.cs +++ b/src/VirtoCommerce.OrdersModule.Data/Search/Indexed/CustomerOrderChangesProvider.cs @@ -16,14 +16,13 @@ public class CustomerOrderChangesProvider : IIndexDocumentChangesProvider { public const string ChangeLogObjectType = nameof(CustomerOrder); - protected Func OrderRepositoryFactory { get; } - - protected IChangeLogSearchService ChangeLogSearchService { get; } + private readonly Func _orderRepositoryFactory; + private readonly IChangeLogSearchService _changeLogSearchService; public CustomerOrderChangesProvider(Func orderRepositoryFactory, IChangeLogSearchService changeLogSearchService) { - OrderRepositoryFactory = orderRepositoryFactory; - ChangeLogSearchService = changeLogSearchService; + _orderRepositoryFactory = orderRepositoryFactory; + _changeLogSearchService = changeLogSearchService; } public virtual Task> GetChangesAsync(DateTime? startDate, DateTime? endDate, long skip, long take) @@ -38,15 +37,15 @@ public virtual async Task GetTotalChangesCountAsync(DateTime? startDate, D if (startDate == null && endDate == null) { // Get total products count - using var repository = OrderRepositoryFactory(); + using var repository = _orderRepositoryFactory(); return await repository.CustomerOrders.CountAsync(); } - var criteria = GetChangeLogSearchCriteria(startDate, endDate, 0, 0); + var criteria = GetChangeLogSearchCriteria(startDate, endDate, skip: 0, take: 0); // Get changes count from operation log - return (await ChangeLogSearchService.SearchAsync(criteria)).TotalCount; + return (await _changeLogSearchService.SearchAsync(criteria)).TotalCount; } /// @@ -54,16 +53,16 @@ public virtual async Task GetTotalChangesCountAsync(DateTime? startDate, D /// protected virtual async Task> GetChangesFromRepositoryAsync(long skip, long take) { - using var repository = OrderRepositoryFactory(); + using var repository = _orderRepositoryFactory(); - var productIds = await repository.CustomerOrders + var orders = await repository.CustomerOrders .OrderBy(x => x.CreatedDate) .Select(x => new { x.Id, ModifiedDate = x.ModifiedDate ?? x.CreatedDate }) .Skip((int)skip) .Take((int)take) .ToArrayAsync(); - return productIds + return orders .Select(x => new IndexDocumentChange { @@ -71,7 +70,7 @@ protected virtual async Task> GetChangesFromRepositor ChangeType = IndexDocumentChangeType.Modified, ChangeDate = x.ModifiedDate, }) - .ToArray(); + .ToList(); } /// @@ -80,7 +79,7 @@ protected virtual async Task> GetChangesFromRepositor protected virtual async Task> GetChangesFromOperationLogAsync(DateTime? startDate, DateTime? endDate, long skip, long take) { var criteria = GetChangeLogSearchCriteria(startDate, endDate, skip, take); - var operations = (await ChangeLogSearchService.SearchAsync(criteria)).Results; + var operations = (await _changeLogSearchService.SearchAsync(criteria)).Results; return operations .Select(x => @@ -90,7 +89,7 @@ protected virtual async Task> GetChangesFromOperation ChangeType = x.OperationType == EntryState.Deleted ? IndexDocumentChangeType.Deleted : IndexDocumentChangeType.Modified, ChangeDate = x.ModifiedDate ?? x.CreatedDate, }) - .ToArray(); + .ToList(); } protected virtual ChangeLogSearchCriteria GetChangeLogSearchCriteria(DateTime? startDate, DateTime? endDate, long skip, long take) @@ -99,7 +98,7 @@ protected virtual ChangeLogSearchCriteria GetChangeLogSearchCriteria(DateTime? s var types = AbstractTypeFactory.AllTypeInfos.Select(x => x.TypeName).ToList(); - if (types.Count != 0) + if (types.Count > 0) { types.Add(ChangeLogObjectType); criteria.ObjectTypes = types; From 8379121f944a15bbacb83c5eeef4ffe3a8f257ff Mon Sep 17 00:00:00 2001 From: Artem Dudarev Date: Thu, 18 Dec 2025 19:10:34 +0200 Subject: [PATCH 8/8] Fix naming --- .../Handlers/LogChangesOrderChangedEventHandler.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/VirtoCommerce.OrdersModule.Data/Handlers/LogChangesOrderChangedEventHandler.cs b/src/VirtoCommerce.OrdersModule.Data/Handlers/LogChangesOrderChangedEventHandler.cs index 43c361431..bb364199c 100644 --- a/src/VirtoCommerce.OrdersModule.Data/Handlers/LogChangesOrderChangedEventHandler.cs +++ b/src/VirtoCommerce.OrdersModule.Data/Handlers/LogChangesOrderChangedEventHandler.cs @@ -24,7 +24,7 @@ public class LogChangesOrderChangedEventHandler : IEventHandler> _auditablePropertiesCacheByTypeDict = new(); + private static readonly ConcurrentDictionary> _auditablePropertiesListByTypeDict = new(); public LogChangesOrderChangedEventHandler(IChangeLogService changeLogService, IMemberService memberService, ISettingsManager settingsManager) { @@ -135,7 +135,7 @@ protected static List GetAuditableProperties(GenericChangedEntry + return _auditablePropertiesListByTypeDict.GetOrAdd(type, t => t.GetProperties() .Where(prop => Attribute.IsDefined(prop, typeof(AuditableAttribute))) .Select(x => x.Name)