DirectumRX. Подробная история в карточке сущности

50 9

Как известно, на вкладке "История" записываются действия, выполненные пользователями (в том числе и системными) при работе с сущностью, однако, конкретика изменения тех или иных реквизитов остается "за кадром". Чтобы конкретизировать, какой реквизит был изменен, и записать в историю значения до и после изменения - нужно немного дописать событие "До сохранения истории".

public override void BeforeSaveHistory(Sungero.Content.DocumentHistoryEventArgs e)
{
  // Вызываем базовый обработчик
  base.BeforeSaveHistory(e);

  // Нам нужно записывать историю, только если произошло обновление
  var isUpdateAction = e.Action == Sungero.CoreEntities.History.Action.Update;
  if (!isUpdateAction)
	return;
  
  // Далее мы отбираем те свойства, для которых будем записывать данные в историю, 
  // нас не интересует некоторые системные свойства, и свойства-коллекции
  var properties = _obj.State.Properties.Where(p =>
					p != _obj.State.Properties.Index &&
					p != _obj.State.Properties.Modified &&
					p != _obj.State.Properties.DocumentDate &&
					p != _obj.State.Properties.Versions &&
					p != _obj.State.Properties.Tracking);
  
  var propertiesType = Сontracts.Info.Properties.GetType();
  var objType = _obj.GetType();
  // Чтобы в истории, в колонке "Действие" отобразилось конкретное наименование - нам нужно
  // создать Enumeration этого действия и создать под него строку локализации с префиксом 
  // Enum_Operation_ (для нашего примера - Enum_Operation_SDChange, где будет прописано желаемое 
  // имя действия, например "Изменение реквизита". Если этого не сделать - в колонке "Действие" будет пусто.
  var operation = new Enumeration("SDChange");
  
  // Обходим коллекцию свойств
  foreach (var property in properties)
  {
	// Берем в работу только те свойства, которые были помечены как измененные
	var isChanged = (property as Sungero.Domain.Shared.IPropertyState).IsChanged;        
	if (isChanged)
	{
	  // Извлечение локализованного наименования реквизита.
	  var propertyName = (property as Sungero.Domain.Shared.PropertyStateBase).PropertyName;
	  var propertyInfo = propertiesType.GetProperty(propertyName).GetValue(Сontracts.Info.Properties);
	  var name = propertyInfo.GetType().GetProperty("LocalizedName").GetValue(propertyInfo);
	  
	  // Извлечение нового значения реквизита.
	  var newValue = objType.GetProperty(propertyName).GetValue(_obj);
	  
	  // Извлечение старого значения реквизита.
	  var oldValue = property.GetType().GetProperty("OriginalValue").GetValue(property);
	  
	  // Дополнительно еще раз сверяет что старое и новое значения не равны, также отбрасываем изменения строковых свойств с null на пустую строку
	  if (newValue == oldValue ||
		  newValue != null && oldValue != null && Equals(newValue.ToString(), oldValue.ToString()) ||
		  newValue != null && oldValue == null && string.IsNullOrEmpty(newValue.ToString()) ||
		  oldValue != null && newValue == null && string.IsNullOrEmpty(oldValue.ToString()))
		continue;
	  
	  // Записываем данные в историю
	  var comment = string.Format("{0}. Новое значение: {1}. Прежнее значение: {2}", name, newValue, oldValue);
	  e.Write(operation, null, comment);
	}
  }

}

Таким образом в карточке сущности появится подробная детализация изменения данных сущности, что позволит отслеживать изменения более тщательно.

50
Авторизуйтесь, чтобы оценить материал.
9
Александр Волошин

Добрый день. Есть 2 вопроса по представленному решению:

  1. Как Вы отслеживаете изменение в коллекциях?
  2. Что записывается в историю для свойств-перечислений: их английское название или локализованное?
Денис Зайцев

Александр, добрый день

1. В данном примере никак. Заказчику предложен вариант с информацией, что свойство-коллекция просто изменилось, без новых и прежних значений. И к каждому такому свойству приходится обращаться персонально

2. Спасибо за вопрос. В данном примере значение самого свойства. Не отображаемое имя, а само имя значения. Например для InternalApprovalState будет указано OnApproval, а не On approval или На согласовании.

Может кто подскажет как обратиться к типу свойства и в случае с перечислением к методу GetLocalizedValue. Или к свойоствам коллекции.

Сергей Беляков

АлександрДенис.

Если автор не против, то я могу накидать статью с ответами на вопросы про Enumeration типы и коллекции, со ссылкой на Вашу статью.

Денис Зайцев

Сергей, автор не будет против)

Станислав Егоров

Подскажите, где конкретно и что надо создать чтобы вместо пустоты действия появился текст?
по комментарию так и не понял где искать это перечисление действий истории. 

Денис Зайцев

Станислав, Для операции SDChange необходимо создать ресурс в вашей карточке с префиксом Enum_Operation_Изображение

Игорь Блинов

Добрый день!

Спасибо большое за сценарий!

Всё хорошо, но при получении свойств периодически подалась ошибка "Ambiguous match found".

Исправлена заменой 

GetProperty(propertyName)

на 

GetProperties().Where(p => p.Name == propertyName).First();

При желании можно также найти какой из нескольких найденных типов свойства больше подходит, но работает уже и так. 

Игорь Блинов: обновлено 03.01.2025 в 15:30
Евгений Григорьев

Сергей, если статья появилась, дайте ссылку пожалуйста.

Юрий Фомин

Добрый день!

Возникла аналогичная задача. Решил написать универсальную серверную функцию, что бы в дальнейшем использовать для разных сущностей.  Код функции будет в конце. Функция вызываем в:

public override void BeforeSaveHistory(Sungero.Domain.HistoryEventArgs e)
    {
      base.BeforeSaveHistory(e);
      Решение.Модуль.Functions.Module.HistoryDetailsSave(e, _obj);
    } 

На текущий момент закрывает потребность, но хотелось бы решить с коллекциями: фиксировать удаление и изменения строк коллекции, отображая значения свойств. Сейчас пока фиксируется добавление строки и изменение. 

Кто и как решает вопрос с коллекциями? Возможно подскажите доп. функциональность, которую не предусмотрел для создания на будущее более универсальной функции.

public void HistoryDetailsSave(Sungero.Domain.HistoryEventArgs e, Sungero.Domain.Shared.IEntity entity)
    {
      if (e.Action != Sungero.CoreEntities.History.Action.Update)
      {
        return;
      }
      foreach (var property in entity.State.Properties)
      {
        var propStateBase = property as Sungero.Domain.Shared.PropertyStateBase;
        var prop = property as Sungero.Domain.Shared.IPropertyState;
        if (propStateBase == null || !prop.IsChanged)
        {
          continue;
        }
        string sysName = propStateBase.PropertyName;
        var propertyInfo = entity.Info.Properties.GetType().GetProperty(sysName)?.GetValue(entity.Info.Properties);
        if (propertyInfo == null)
        {
          continue;
        }
        string localizedName = propertyInfo?.GetType().GetProperty("LocalizedName")?.GetValue(propertyInfo)?.ToString() ?? sysName;
        if (propertyInfo is Sungero.Domain.Shared.ICollectionPropertyInfo)
        {
          var collectionObj = entity.GetType().GetProperty(sysName)?.GetValue(entity) as System.Collections.IEnumerable;
          if (collectionObj != null)
          {
            foreach (Sungero.Domain.Shared.IChildEntity row in collectionObj)
            {
              if (row != null && row.State.IsChanged)
              {
                if (row.State.IsInserted)
                {
                  e.Write(e.Operation, e.OperationDetailed, $"{localizedName}: Добавлена строка");
                }
                else if (row.State.IsChanged)
                {
                  e.Write(e.Operation, e.OperationDetailed, $"{localizedName}: Строка изменена");
                }
              }
            }
          }
          continue;
        }
        var originalValueObj = property.GetType().GetProperty("OriginalValue")?.GetValue(property);
        string oldValue = originalValueObj != null ? originalValueObj.ToString() : "[Пусто]";
        var valueObj = entity.GetType().GetProperty(sysName)?.GetValue(entity);
        string newValue = valueObj != null ? valueObj.ToString() : "[Пусто]";
        if (originalValueObj is Enumeration || valueObj is Enumeration)
        {
          if (propertyInfo is Sungero.Domain.Shared.EnumPropertyInfo enumPropertyInfo)
          {
            if (originalValueObj is Enumeration oldEnum)
            {
              oldValue = enumPropertyInfo.GetLocalizedValue(oldEnum) ?? oldValue;
            }
            if (valueObj is Enumeration newEnum)
            {
              newValue = enumPropertyInfo.GetLocalizedValue(newEnum) ?? newValue;
            }
          }
        }
        if (oldValue == newValue || (oldValue == "[Пусто]" && string.IsNullOrEmpty(newValue)))
        {
          continue;
        }
        e.Write(e.Operation, e.OperationDetailed, $"{localizedName}: '{oldValue}' -> '{newValue}'");
      }
    }

 

Авторизуйтесь, чтобы написать комментарий