[ELMA3] Добавление кастомных колонок при экспорте записей в Excel

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

Точка расширения

Чтобы экспортировать в Excel свойства, которые отсутствуют в сущности, следует использовать точку расширения IExportExcelCustomColumnsProvider, которая позволяет добавить при экспорте свои колонки в базовый набор колонок.

/// <summary>
    /// Провайдер для кастомных столбцов при экспорте в Excel
    /// </summary>
    [ExtensionPoint(ServiceScope.Shell)]
    public interface IExportExcelCustomColumnsProvider : IExportExcelValueProvider
    {
        /// <summary>
        /// Получить расширенный список столбцов для экспорта
        /// </summary>
        /// <param name="type">Тип сущности</param>
        /// <param name="baseColumns">Базовый список столбцов, необходим для корректного встраивания кастомных столбцов</param>
        /// <returns>Новый список столбцов со встроенными кастомными</returns>
        IEnumerable<ExportColumnDescription> GetColumns(Type type, IEnumerable<ExportColumnDescription> baseColumns);
    }

Пример реализации

В отчете о трудозатратах выводятся колонки Название и План.

Однако, при экспорте в Excel данные колонки отсутствуют. Причина в том, что сущность трудозатрат WorkLogItem не содержит соответствующих свойств.

Чтобы экспортировать необходимые свойства, добавляется реализация точки расширения IExportExcelCustomColumnsProvider, которая для типа IWorkLogItem встраивает в нужном порядке недостающие колонки, а также позволяет вернуть значение свойства для кастомной ячейки и при необходимости конвертирует его (например, преобразует значение типа WorkTime в TimeSpan).

    /// <summary>
    /// Провайдер для кастомных столбцов трудозатрат при экспорте в Excel
    /// </summary>
    [Component]
    internal sealed class WorkLogItemExportExcelCustomColumnsProvider : IExportExcelCustomColumnsProvider
    {
        #region Private members

        /// <summary>
        /// Максимальное число минут в типе Timespan
        /// </summary>
        private static readonly long MaxTotalMinutes = (long)TimeSpan.MaxValue.TotalMinutes;

        /// <summary>
        /// Гуид свойства Наименование
        /// </summary>
        private static readonly Guid NameUid = new Guid("26401CA9-3D5A-4231-9E8C-2EA0DD04D121");

        /// <summary>
        /// Гуид свойства Плановые трудозатраты
        /// </summary>
        private static readonly Guid PlanWorkTimeUid = new Guid("99334918-3FE6-465E-87C3-35363073B5AE");

        /// <summary>
        /// Гуид свойства Статус
        /// </summary>
        private static readonly Guid StatusUid = new Guid("d671f637-3a5a-4c08-8b55-a1f3cb622027");

        /// <summary>
        /// Гуид свойства Вид деятельности
        /// </summary>
        private static readonly Guid ActivityUid = new Guid("50417934-90c5-46b9-91be-d2935eceeabc");

        /// <summary>
        /// Описание кастомного столбца Название
        /// </summary>
        private readonly ExportColumnDescription nameColumn = new ExportColumnDescription()
        {
            ColumnName = SR.T("Название"),
            TableColumnWidth = 200,
            PropertyUid = NameUid
        };

        /// <summary>
        /// Описание кастомного столбца Плановые трудозатраты
        /// </summary>
        private readonly ExportColumnDescription planWorkTimeColumn = new ExportColumnDescription()
        {
            ColumnName = SR.T("Плановые трудозатраты"),
            TableColumnWidth = 200,
            PropertyUid = PlanWorkTimeUid
        };

        #endregion

        #region Implementation IExportExcelCustomColumnsProvider

        /// <inheritdoc />
        public IEnumerable<ExportColumnDescription> GetColumns(Type type, IEnumerable<ExportColumnDescription> baseColumns)
        {
            List<ExportColumnDescription> result = null;
            if (type.IsInheritOrSame<IWorkLogItem>())
            {
                result = baseColumns.ToList();

                // Название вставляем после статуса или первым.
                var insertIndex = result.FindIndex(col => col.PropertyUid == StatusUid) + 1;
                result.Insert(insertIndex, nameColumn);

                // Плановые трудозатраты вставляем после вида деятельности или последним.
                insertIndex = result.FindIndex(col => col.PropertyUid == ActivityUid) + 1;
                if (insertIndex == 0)
                {
                    result.Add(planWorkTimeColumn);
                }
                else
                {
                    result.Insert(insertIndex, planWorkTimeColumn);
                }
            }
            return result;
        }
        #endregion

        #region Implementation IExportExcelValueProvider

        /// <inheritdoc />
        public object GetValue(IEntity entity, Guid propertyUid)
        {
            object result = null;
            var item = (IWorkLogItem)entity;

            if (propertyUid == NameUid)
            {
                result = item.Name;
            }
            else if (propertyUid == PlanWorkTimeUid)
            {
                result = ConvertWorkTime(item.PlanWorkTime);
            }
            else
            {
                // nothing to do.
            }
            return result;
        }

        /// <inheritdoc />
        public bool Resolve(IEntity entity, Guid propertyUid)
        {
            return entity is IWorkLogItem && propertyUid.In(NameUid, PlanWorkTimeUid);
        }
        #endregion

        /// <summary>
        /// Конвертируем <see cref="WorkTime"/> в формат для выгрузки
        /// </summary>
        /// <param name="value">Конвертируемое значение</param>
        /// <returns>Объект типа Timespan или string</returns>
        private object ConvertWorkTime(WorkTime? value)
        {
            if (value != null)
            {
                var workTime = value.Value;
                var totalMinutes = workTime.TotalMinutes;
                if (totalMinutes <= MaxTotalMinutes)
                {
                    return TimeSpan.FromMinutes(totalMinutes);
                }
                else
                {
                    // В случае переполнения выведем хотя бы просто строку (пусть она в сумме и не учтется потом).
                    return string.Concat(workTime.TotalHours, ":", workTime.Minutes);
                }
            }
            return TimeSpan.Zero;
        }
    }

В приведенном примере встроены колонки Название и Плановые трудозатраты.