Решаемая проблема
Периодически у меня возникала следующая задача: необходимо было разработать некий расчетный инструмент или инструмент планирования, который рассчитывает на основании каких-то данных некие показатели, а пользователь должен иметь возможность при необходимости их скорректировать и зафиксировать в системе итоговый результат.
И в этот момент возникает проблема, поскольку этот инструмент с одной стороны является во многом аналитическим и должен предоставлять пользователю информацию для принятия решений в максимально удобной форме, а с другой стороны - должен позволять в удобном виде редактировать информацию и записывать ее в систему.
Для первого в 1С идеально подходит отчет СКД, а для второго - таблица на форме (источник данных для нее не особенно важен). Дилемма...
А самое страшное - у пользователей уже есть богатый опыт работы с инструментом, который объединяет удобную форму представления данных, алгоритмы расчета и сохранение этих данных. Это Excel - вечный укор одинэсникам :)
И пользователи искренне недоумевают, почему "в вашей программе" нельзя сделать также. И в самом деле - почему? :)
Принципы работы
Идея плавает на поверхности и на оригинальность не претендует. Некоторые из используемых решений компромиссные, имеется ряд ограничений.
В отчете СКД одна из группировок "назначается" ключевой (допустим, "Номенклатура"). Именно по ней пользователь будет редактировать значения ресурсов.
Возможна детализация этой группировки какой-то вертикальной группировкой (чаще всего необходима детализация по периоду).
Естественно, кроме ключевой горизонтальной группировки в отчете могут быть как вышестоящие, так и нижестоящие группировки (результаты по ключевой группировке можно хранить и получать в разрезе вышестоящих группировок).
Сразу после компоновки полученный результат отчета автоматически анализируется, определяется местонахождение нужных строк и колонок, значения ключевой группировки и ресурсов. Местонахождение колонок ресурсов производится по представлениям полей ресурсов в заголовке отчета либо по данным расшифровки (тогда их необходимо задать через макеты СКД). Значения ресурсов отчета также изначально берутся из текста ячеек.
Результат анализа со всеми полученными данными и их координатами фиксируется в отдельной структуре данных, где отражаются и все последующие изменения данных отчета. Когда пользователь редактирует на форме отчета значение любого из ресурсов ключевой группировки, эти изменения фиксируются в структуре данных (с возможным пересчетом связанных ячеек при необходимости - да-да, почти как в Excel). Редактирование происходит непосредственно в ячейках отчета (для этого после компоновки требуемые ячейки переоформляются на содержащие значения).
Поддерживается пересчет итогов по вышестоящим группировкам и общих вертикальных итогов. Пересчет горизонтальных итогов пока не предусмотрен.
Чтобы инструмент был максимально отзывчивым - количество серверных вызовов минимизировано. Непосредственно редактирование отчета серверных вызовов не производит.
Использование инструмента (или как сделать обычный отчет СКД - редактируемым)
Старался сделать внедрение минимально инвазивным
1) необходимо в ПриКомпоновкеРезультата добавить блок инициализации и настройки параметров редактируемого отчета (пример будет ниже)
2) на форме отчета необходимо в обработчик ПриИзмененииСодержимогоОбласти() добавить вызов одноименной процедуры (из общего модуля)
В принципе, это почти и все :)
Если необходимо добавить формулы для пересчета связанных ячеек – то одним из параметров при изменении содержимого области передается ссылка на свой обработчик, который будет вызываться после редактирования пользователем значения ресурса. В этом обработчике можно будет написать обычный код 1С по расчету связанных данных, после чего необходимо будет вызвать ПриИзмененииЗначенияРесурса() для отражения изменений (включая пересчет итогов). Эффект будет аналогичен экселевскому.
По-хорошему, не помешает в отчет добавить пару "красивостей", типа предупреждения о наличии измененных данных при попытке перекомпоновать или закрыть отчет (уже есть в прилагаемой демке)
Записать измененные данные в систему по команде пользователя не составляет никакой сложности, поскольку вот они - уже лежат структурированные в памяти (для удобства добавлена ПолучитьТаблицуРезультата(), позволяющая получать "слепок" данных отчета в привычную таблицу значений).
Пример настройки редактируемого отчета в ПриКомпоновкеРезультата (из демо-конфигурации):
// инициализация структуры редактируемого отчета и его настройка
СтруктураОтчета = РедактируемыеОтчетыСервер.ИнициализироватьСтруктуруОтчета(КомпоновщикНастроек, "Номенклатура", "Период"); // ключевые горизонтальная и вертикальная группировки
СтруктураОтчета.ДопГоризонтальныеГруппировки = Новый Структура("ГруппаНоменклатуры", Истина); // дополнительная вышестоящая группировка (с признаком пересчетом итогов по ней)
СтруктураОтчета.ИспользуютсяОбщиеВертикальныеИтогиВерх = Ложь;
СтруктураОтчета.ИспользуютсяОбщиеВертикальныеИтогиНиз = Истина;
// добавление в структуру отчета описания ресурсов
РедактируемыеОтчетыСервер.ДобавитьРесурсВСтруктуруОтчета(КомпоновщикНастроек, "Количество", Новый ОписаниеТипов("Число", Новый КвалификаторыЧисла(12,1)), "ЧЦ=12; ЧДЦ=0", "Количество", Истина, Истина);
РедактируемыеОтчетыСервер.ДобавитьРесурсВСтруктуруОтчета(КомпоновщикНастроек, "Сумма", Новый ОписаниеТипов("Число", Новый КвалификаторыЧисла(14,2)), "ЧЦ=14; ЧДЦ=2", "Сумма", Истина, Ложь);
// извлечение необходимых данных при компоновке отчета
РедактируемыеОтчетыСервер.ПриКомпоновкеРезультата(КомпоновщикНастроек, ДокументРезультат, ДанныеРасшифровки, СхемаКомпоновкиДанных, СтандартнаяОбработка);
Другие существующие на текущий момент возможности по дополнительной настройке редактируемого отчета можно посмотреть в процедуре ИнициализироватьСтруктуруОтчета()
Ограничения инструмента на текущий момент (те, что вспомнил)
1) редактирование ресурсов пока предусмотрено только для какой-то одной простой горизонтальной группировки (опционально с разверткой вправо по периодам или другой вертикальной группировке).
2) предполагается, что структура отчета СКД довольно стандартная. Горизонтальные группировки отчета выводятся в первой ячейке табличного документа (обычно так и бывает). Шапка отчета тоже сформирована стандартным образом (при анализе определяется местоположение шапки). Форматирование числовых ресурсов в отчете является стандартным в части возможности штатного преобразования строки в число.
3) пока предусмотрены только два типа редактируемых ресурсов - "Число" и "Дата". При этом ресурсы с пересчетом по вышестоящим группировкам могут быть только числовые.
Демо-конфигурация
В приложенной демо-конфигурации общая функциональность вынесена в общие модуля, что выглядит предпочитаемым способом использования.
Скачивать демо-конфигурацию за старт-мани вовсе не обязательно. Ниже привожу все функции/процедуры в полном объеме. Демка - просто удобный способ посмотреть на все это "добро" в действии. Демо-конфигурация выполнена на релизе 8.3.14 и приложена в виде файла cf для универсальности (тестовые данные в базе не содержатся).
После запуска демо-конфигурации необходимо открыть и сформировать демо-отчет. "Количество" в строках номенклатуры можно редактировать непосредственно. Нажатие кнопки "Вывести результат на экран" производит вывод через Сообщить() таблицы значений с отредактированными данными отчета.
Общий модуль "РедактируемыеОтчетыКлиент":
Процедура ПриИзмененииЗначенияРесурса(ФормаОтчета, НомерСтроки, ИмяРесурса, Знач СтароеЗначение, Знач НовоеЗначение, ИндексВертикальнойГруппировки = 0) Экспорт
СтруктураОтчета = ФормаОтчета.Отчет.КомпоновщикНастроек.Настройки.ДополнительныеСвойства.СтруктураОтчета;
ТабличныйДокумент = ФормаОтчета.Результат;
Если СтароеЗначение = НовоеЗначение Тогда
Возврат;
КонецЕсли;
СтруктураОтчета.СтрокиГоризонтальнойКлючевойГруппировки[НомерСтроки][ИмяРесурса][ИндексВертикальнойГруппировки] = НовоеЗначение;
СтруктураОтчета.ДанныеИзменены = Истина;
НомерКолонки = СтруктураОтчета.НомераКолонокРесурсов[ИмяРесурса][ИндексВертикальнойГруппировки];
ФорматЗначения = СтруктураОтчета.ФорматыРесурсов[ИмяРесурса];
Ячейка = ТабличныйДокумент.Область(НомерСтроки, НомерКолонки);
Если Ячейка.Значение <> НовоеЗначение Тогда
Ячейка.Значение = НовоеЗначение;
КонецЕсли;
Ячейка.ЦветФона = СтруктураОтчета.ЦветРедактированныхЯчеек;
// пересчет итогов отчета
Если СтруктураОтчета.РесурсыСПересчетомИтогов.Свойство(ИмяРесурса) Тогда
// обновление общих итогов
Если СтруктураОтчета.ИспользуютсяОбщиеВертикальныеИтогиВерх Тогда
ЯчейкаОбщегоИтогаВерх = ТабличныйДокумент.Область(СтруктураОтчета.НомерСтрокиОбщихИтоговВерх, НомерКолонки);
ЯчейкаОбщегоИтогаВерх.Значение = ЯчейкаОбщегоИтогаВерх.Значение + НовоеЗначение - СтароеЗначение;
КонецЕсли;
Если СтруктураОтчета.ИспользуютсяОбщиеВертикальныеИтогиНиз Тогда
ЯчейкаОбщегоИтогаНиз = ТабличныйДокумент.Область(СтруктураОтчета.НомерСтрокиОбщихИтоговНиз, НомерКолонки);
ЯчейкаОбщегоИтогаНиз.Значение = ЯчейкаОбщегоИтогаНиз.Значение + НовоеЗначение - СтароеЗначение;
КонецЕсли;
// обновление итогов вышестоящих группировок (снизу - вверх)
ДопГоризонтальныеГруппировки = Новый Структура;
СтартовыйИндекс = СтруктураОтчета.НомераСтрокВсехГоризонтальныхГруппировок.Найти(НомерСтроки) - 1;
Для ОтрицательныйИндекс = -СтартовыйИндекс По 0 Цикл
НомерСтрокиГруппировки = СтруктураОтчета.НомераСтрокВсехГоризонтальныхГруппировок[-ОтрицательныйИндекс];
ДанныеДопГоризонтальнойГруппировки = СтруктураОтчета.СтрокиДопГоризонтальныхГруппировок[НомерСтрокиГруппировки];
Если ДанныеДопГоризонтальнойГруппировки = Неопределено Тогда
Продолжить;
КонецЕсли;
Для Каждого ЭлементДанныхДопГоризонтальнойГруппировки Из ДанныеДопГоризонтальнойГруппировки Цикл
ИмяГоризонтальнойГруппировки = ЭлементДанныхДопГоризонтальнойГруппировки.Ключ;
Если СтруктураОтчета.ДопГоризонтальныеГруппировки[ИмяГоризонтальнойГруппировки] Тогда // если доп-группировка пересчитываемая
Если НЕ ДопГоризонтальныеГруппировки.Свойство(ИмяГоризонтальнойГруппировки) Тогда
ДопГоризонтальныеГруппировки.Вставить(ИмяГоризонтальнойГруппировки, НомерСтрокиГруппировки);
КонецЕсли;
КонецЕсли;
КонецЦикла;
Если ДопГоризонтальныеГруппировки.Количество() = СтруктураОтчета.ДопГоризонтальныеГруппировки.Количество() Тогда
Прервать;
КонецЕсли;
КонецЦикла;
Для Каждого ЭлементДопГоризонтальнойГруппировки Из ДопГоризонтальныеГруппировки Цикл
ЯчейкаИтогаГруппировки = ТабличныйДокумент.Область(ЭлементДопГоризонтальнойГруппировки.Значение, НомерКолонки);
ЯчейкаИтогаГруппировки.Значение = ЯчейкаИтогаГруппировки.Значение + НовоеЗначение - СтароеЗначение;
ЯчейкаИтогаГруппировки.ЦветФона = СтруктураОтчета.ЦветРедактированныхЯчеек;
КонецЦикла;
КонецЕсли;
// к сожалению, изменение дополнительных свойств компоновщика приводит к установке признака изменения варианта отчета и необходимо убрать последствия
СброситьПризнакИзмененияВариантаОтчета(ФормаОтчета, Ложь);
КонецПроцедуры
Процедура ПриИзмененииСодержимогоОбласти(ФормаОтчета, ОповещениеРедактированияОтчета = Неопределено) Экспорт
СтруктураОтчета = ФормаОтчета.Отчет.КомпоновщикНастроек.Настройки.ДополнительныеСвойства.СтруктураОтчета;
Если НЕ СтруктураОтчета.Инициализирована Тогда
ПоказатьПредупреждение(, "Обработка невозможна - структура отчета не была инициализирована корректно");
Возврат;
КонецЕсли;
Ячейка = ФормаОтчета.Элементы.Результат.ТекущаяОбласть;
НомерТекущейКолонки = Ячейка.Лево;
НомерТекущейСтроки = Ячейка.Верх;
ДанныеСтроки = СтруктураОтчета.СтрокиГоризонтальнойКлючевойГруппировки.Получить(НомерТекущейСтроки);
Если ДанныеСтроки <> Неопределено Тогда
// определим имя текущей колонки и индекс вертикальной группировки
ИмяРесурса = Неопределено;
ИндексВертикальнойГруппировки = 0;
Для Каждого ЭлементНомераКолонкиРесурса Из СтруктураОтчета.НомераКолонокРесурсов Цикл
Для Каждого НомерКолонки Из ЭлементНомераКолонкиРесурса.Значение Цикл
Если НомерКолонки = НомерТекущейКолонки Тогда
ИмяРесурса = ЭлементНомераКолонкиРесурса.Ключ;
ИндексВертикальнойГруппировки = ЭлементНомераКолонкиРесурса.Значение.Найти(НомерТекущейКолонки);
Прервать;
КонецЕсли;
КонецЦикла;
Если ИмяРесурса <> Неопределено Тогда
Прервать;
КонецЕсли;
КонецЦикла;
// если колонка редактируемая
Если ИмяРесурса <> Неопределено И СтруктураОтчета.РедактируемыеРесурсы.Свойство(ИмяРесурса) Тогда
СтароеЗначение = СтруктураОтчета.СтрокиГоризонтальнойКлючевойГруппировки[НомерТекущейСтроки][ИмяРесурса][ИндексВертикальнойГруппировки];
НовоеЗначение = Ячейка.Значение;
Если СтароеЗначение <> НовоеЗначение Тогда
ПриИзмененииЗначенияРесурса(ФормаОтчета, НомерТекущейСтроки, ИмяРесурса, СтароеЗначение, Ячейка.Значение, ИндексВертикальнойГруппировки);
ОповещениеРедактированияОтчета = ОповещениеРедактированияОтчета;
Если ОповещениеРедактированияОтчета <> Неопределено Тогда
ДопПараметры = Новый Структура;
ДопПараметры.Вставить("СтруктураОтчета", СтруктураОтчета);
ДопПараметры.Вставить("НомерСтроки", НомерТекущейСтроки);
ДопПараметры.Вставить("ИмяРесурса", ИмяРесурса);
ДопПараметры.Вставить("ИндексВертикальнойГруппировки", ИндексВертикальнойГруппировки);
ДопПараметры.Вставить("СтароеЗначение", СтароеЗначение);
ДопПараметры.Вставить("НовоеЗначение", НовоеЗначение);
ВыполнитьОбработкуОповещения(ОповещениеРедактированияОтчета, ДопПараметры);
КонецЕсли;
КонецЕсли;
КонецЕсли;
КонецЕсли;
КонецПроцедуры
Процедура СброситьПризнакИзмененияВариантаОтчета(ФормаОтчета, СброситьПризнакИзмененияДанныхОтчета = Истина) Экспорт
Если СброситьПризнакИзмененияДанныхОтчета Тогда
ФормаОтчета.Отчет.КомпоновщикНастроек.Настройки.ДополнительныеСвойства.СтруктураОтчета.ДанныеИзменены = Ложь;
КонецЕсли;
ФормаОтчета.Элементы.Результат.ОтображениеСостояния.ДополнительныйРежимОтображения = ДополнительныйРежимОтображения.НеИспользовать;
ФормаОтчета.Элементы.Результат.ОтображениеСостояния.Текст = "";
ФормаОтчета.ВариантМодифицирован = Ложь;
КонецПроцедуры
Общий модуль "РедактируемыеОтчетыСервер":
// вызывается в ПриКомпоновкеРезультата
// возвращает значения ресурсов по строке группировки и преобразует ячейки отчета в редактируемые
Функция ПолучитьЗначенияРесурсовСтрокиОтчета(СтруктураОтчета, ТабличныйДокумент, НомерСтроки, ЭтоРедактируемаяГоризонтальнаяГруппировка = Ложь)
СтруктураРесурсов = Новый Структура;
НомераКолонокРесурсов = СтруктураОтчета.НомераКолонокРесурсов;
ТипыРесурсов = СтруктураОтчета.ТипыРесурсов;
Для Каждого ЭлементНомераКолонки Из НомераКолонокРесурсов Цикл
ИмяРесурса = ЭлементНомераКолонки.Ключ;
ИндексВертикальнойКлючевойГруппировки = 0;
Для Каждого НомерКолонки Из НомераКолонокРесурсов[ИмяРесурса] Цикл
Ячейка = ТабличныйДокумент.Область(НомерСтроки, НомерКолонки);
ТипЯчейки = ТипыРесурсов[ЭлементНомераКолонки.Ключ];
Значение = Ячейка.Текст;
Если ТипЯчейки.СодержитТип(Тип("Число")) Тогда
Значение = ?(ПустаяСтрока(Значение), 0, Число(Значение));
ИначеЕсли ТипЯчейки.СодержитТип(Тип("Дата")) Тогда
Если ПустаяСтрока(Значение) ИЛИ Найти(Значение, ".") = 0 ИЛИ СтрДлина(Значение) < 10 Тогда
Значение = Дата(1,1,1);
Иначе
Значение = Дата(Сред(Значение,7,4)+Сред(Значение,4,2)+Сред(Значение,1,2));
КонецЕсли;
КонецЕсли;
// преобразуем ячейку в содержащую значение
Ячейка.СодержитЗначение = Истина;
Ячейка.ТипЗначения = ТипЯчейки;
Ячейка.Значение = Значение;
Если ЭтоРедактируемаяГоризонтальнаяГруппировка И СтруктураОтчета.РедактируемыеРесурсы.Свойство(ИмяРесурса) Тогда
Если СтруктураОтчета.МассивРедактируемыхЗначенийКлючевойВертикальнойГруппировки = Неопределено
ИЛИ СтруктураОтчета.МассивРедактируемыхЗначенийКлючевойВертикальнойГруппировки.Найти(СтруктураОтчета.ЗначенияВертикальнойКлючевойГруппировки[ИндексВертикальнойКлючевойГруппировки]) <> Неопределено Тогда
Ячейка.Защита = Ложь;
КонецЕсли;
КонецЕсли;
Если НЕ СтруктураРесурсов.Свойство(ЭлементНомераКолонки.Ключ) Тогда
СтруктураРесурсов.Вставить(ЭлементНомераКолонки.Ключ, Новый Массив);
КонецЕсли;
СтруктураРесурсов[ЭлементНомераКолонки.Ключ].Добавить(Значение);
ИндексВертикальнойКлючевойГруппировки = ИндексВертикальнойКлючевойГруппировки + 1;
КонецЦикла;
КонецЦикла;
Возврат СтруктураРесурсов;
КонецФункции
// по переданному номеру строки ключевой горизонтальной группировки возвращает структуру, ключи которой - имена вышестоящих группировок, а значения - их значения
Функция ПолучитьЗначенияДопГоризонтальныхГруппировок(СтруктураОтчета, НомерСтрокиОтчета) Экспорт
ЗначенияДопГоризонтальныхГруппировок = Новый Структура;
СтартовыйИндекс = СтруктураОтчета.НомераСтрокВсехГоризонтальныхГруппировок.Найти(НомерСтрокиОтчета) - 1;
Для ОтрицательныйИндекс = -СтартовыйИндекс По 0 Цикл
НомерСтрокиГруппировки = СтруктураОтчета.НомераСтрокВсехГоризонтальныхГруппировок[-ОтрицательныйИндекс];
ДанныеДопГоризонтальнойГруппировки = СтруктураОтчета.СтрокиДопГоризонтальныхГруппировок[НомерСтрокиГруппировки];
Если ДанныеДопГоризонтальнойГруппировки = Неопределено Тогда
Продолжить;
КонецЕсли;
Для Каждого ЭлементДанныхДопГоризонтальнойГруппировки Из ДанныеДопГоризонтальнойГруппировки Цикл
ИмяГоризонтальнойГруппировки = ЭлементДанныхДопГоризонтальнойГруппировки.Ключ;
ЗначениеГоризонтальнойГруппировки = ЭлементДанныхДопГоризонтальнойГруппировки.Значение;
Если НЕ ЗначенияДопГоризонтальныхГруппировок.Свойство(ИмяГоризонтальнойГруппировки) Тогда
ЗначенияДопГоризонтальныхГруппировок.Вставить(ИмяГоризонтальнойГруппировки, ЗначениеГоризонтальнойГруппировки);
КонецЕсли;
КонецЦикла;
Если ЗначенияДопГоризонтальныхГруппировок.Количество() = СтруктураОтчета.ДопГоризонтальныеГруппировки.Количество() Тогда
Прервать;
КонецЕсли;
КонецЦикла;
Возврат ЗначенияДопГоризонтальныхГруппировок;
КонецФункции
// возвращает данные отчета в виде таблицы значений
Функция ПолучитьТаблицуРезультата(КомпоновщикНастроек) Экспорт
СтруктураОтчета = КомпоновщикНастроек.Настройки.ДополнительныеСвойства.СтруктураОтчета;
// создадим таблицу результата
ТаблицаРезультата = Новый ТаблицаЗначений;
ТаблицаРезультата.Колонки.Добавить(СтруктураОтчета.ИмяКлючевойГоризонтальнойГруппировки);
Если ЗначениеЗаполнено(СтруктураОтчета.ИмяКлючевойВертикальнойГруппировки) Тогда
ТаблицаРезультата.Колонки.Добавить(СтруктураОтчета.ИмяКлючевойВертикальнойГруппировки);
КонецЕсли;
Если СтруктураОтчета.ДопГоризонтальныеГруппировки <> Неопределено Тогда
Для Каждого ЭлементГоризонтальнойДопГруппировки Из СтруктураОтчета.ДопГоризонтальныеГруппировки Цикл
ТаблицаРезультата.Колонки.Добавить(ЭлементГоризонтальнойДопГруппировки.Ключ);
КонецЦикла;
КонецЕсли;
Для Каждого ЭлементТипаРесурса Из СтруктураОтчета.ТипыРесурсов Цикл
ТаблицаРезультата.Колонки.Добавить(ЭлементТипаРесурса.Ключ, ЭлементТипаРесурса.Значение);
КонецЦикла;
// заполним таблицу результата
Для Каждого ЭлементГоризонтальнойКлючевойГруппировки Из СтруктураОтчета.СтрокиГоризонтальнойКлючевойГруппировки Цикл
ДанныеГоризонтальнойКлючевойГруппировки = ЭлементГоризонтальнойКлючевойГруппировки.Значение;
Если НЕ ЗначениеЗаполнено(СтруктураОтчета.ИмяКлючевойВертикальнойГруппировки) Тогда // нет вертикальных группировок (читаем одну комбинацию ресурсов)
СтрокаРезультата = ТаблицаРезультата.Добавить();
СтрокаРезультата[СтруктураОтчета.ИмяКлючевойГоризонтальнойГруппировки] = ДанныеГоризонтальнойКлючевойГруппировки[СтруктураОтчета.ИмяКлючевойГоризонтальнойГруппировки];
Для Каждого ЭлементИмениРесурса Из СтруктураОтчета.ИменаРесурсов Цикл
ИмяРесурса = ЭлементИмениРесурса.Ключ;
СтрокаРезультата[ИмяРесурса] = ДанныеГоризонтальнойКлючевойГруппировки[ИмяРесурса][0]
КонецЦикла;
Для Каждого ЗначениеДопГоризонтальнойГруппировки Из ПолучитьЗначенияДопГоризонтальныхГруппировок(СтруктураОтчета, ЭлементГоризонтальнойКлючевойГруппировки.Ключ) Цикл
СтрокаРезультата[ЗначениеДопГоризонтальнойГруппировки.Ключ] = ЗначениеДопГоризонтальнойГруппировки.Значение;
КонецЦикла;
Иначе // есть вертикальные группировки (читаем ресурсы по каждому значению вертикальной группировки)
ИндексВертикальнойКлючевойГруппировки = 0;
Для Каждого ЗначениеВертикальнойКлючевойГруппировки Из СтруктураОтчета.ЗначенияВертикальнойКлючевойГруппировки Цикл
СтрокаРезультата = ТаблицаРезультата.Добавить();
СтрокаРезультата[СтруктураОтчета.ИмяКлючевойГоризонтальнойГруппировки] = ДанныеГоризонтальнойКлючевойГруппировки[СтруктураОтчета.ИмяКлючевойГоризонтальнойГруппировки];
СтрокаРезультата[СтруктураОтчета.ИмяКлючевойВертикальнойГруппировки] = ЗначениеВертикальнойКлючевойГруппировки;
Для Каждого ЭлементИмениРесурса Из СтруктураОтчета.ИменаРесурсов Цикл
ИмяРесурса = ЭлементИмениРесурса.Ключ;
СтрокаРезультата[ИмяРесурса] = ДанныеГоризонтальнойКлючевойГруппировки[ИмяРесурса][ИндексВертикальнойКлючевойГруппировки]
КонецЦикла;
Для Каждого ЗначениеДопГоризонтальнойГруппировки Из ПолучитьЗначенияДопГоризонтальныхГруппировок(СтруктураОтчета, ЭлементГоризонтальнойКлючевойГруппировки.Ключ) Цикл
СтрокаРезультата[ЗначениеДопГоризонтальнойГруппировки.Ключ] = ЗначениеДопГоризонтальнойГруппировки.Значение;
КонецЦикла;
ИндексВертикальнойКлючевойГруппировки = ИндексВертикальнойКлючевойГруппировки + 1;
КонецЦикла;
КонецЕсли;
КонецЦикла;
Возврат ТаблицаРезультата;
КонецФункции
Процедура ПриКомпоновкеРезультата(КомпоновщикНастроек, ТабличныйДокумент, ДанныеРасшифровкиОтчета, СхемаКомпоновкиДанных, СтандартнаяОбработка) Экспорт
// выполним компоновку, если отчет выполняет стандартную компоновку
Если СтандартнаяОбработка Тогда
СтандартнаяОбработка = Ложь;
КомпоновщикМакета = Новый КомпоновщикМакетаКомпоновкиДанных;
МакетКомпоновки = КомпоновщикМакета.Выполнить(СхемаКомпоновкиДанных, КомпоновщикНастроек.ПолучитьНастройки(), ДанныеРасшифровкиОтчета);
ПроцессорКомпоновки = Новый ПроцессорКомпоновкиДанных;
ПроцессорКомпоновки.Инициализировать(МакетКомпоновки,,ДанныеРасшифровкиОтчета);
ТабличныйДокумент.Очистить();
ПроцессорВывода = Новый ПроцессорВыводаРезультатаКомпоновкиДанныхВТабличныйДокумент;
ПроцессорВывода.УстановитьДокумент(ТабличныйДокумент);
ПроцессорВывода.Вывести(ПроцессорКомпоновки);
КонецЕсли;
СтруктураОтчета = КомпоновщикНастроек.Настройки.ДополнительныеСвойства.СтруктураОтчета;
// соответствие ресурсов номерам колонок
НомераКолонокРесурсов = Новый Структура;
Для Каждого ЭлементИмениКолонки Из СтруктураОтчета.ИменаРесурсов Цикл
НомераКолонокРесурсов.Вставить(ЭлементИмениКолонки.Ключ, Неопределено);
КонецЦикла;
// определим высоту заголовка (до начала области расшифровок)
НомерПервойСтрокиРасшифровки = Неопределено;
Для НомерСтроки = 1 По ТабличныйДокумент.ВысотаТаблицы Цикл
Ячейка = ТабличныйДокумент.Область(НомерСтроки, 1);
Если Ячейка.Расшифровка <> Неопределено Тогда
НомерПервойСтрокиРасшифровки = НомерСтроки;
Прервать;
КонецЕсли;
КонецЦикла;
Если НомерПервойСтрокиРасшифровки = Неопределено Тогда
СтруктураОтчета.ТекстОшибкиИнициализации = "Ошибка инициализации структуры отчета";
Возврат;
КонецЕсли;
СтруктураОтчета.Вставить("НомерПервойСтрокиРасшифровки", НомерПервойСтрокиРасшифровки);
// определим номера колонок по их заголовкам или расшифровке
Для НомерКолонки = 1 По ТабличныйДокумент.ШиринаТаблицы Цикл
Для НомерСтроки = 1 По НомерПервойСтрокиРасшифровки + 5 Цикл
Ячейка = ТабличныйДокумент.Область(НомерСтроки, НомерКолонки);
ПоляРасшифровки = Новый Массив;
Если Ячейка.Расшифровка <> Неопределено Тогда
ПоляРасшифровки = ДанныеРасшифровкиОтчета.Элементы[Ячейка.Расшифровка].ПолучитьПоля();
КонецЕсли;
Для Каждого ЭлементИмениКолонки Из СтруктураОтчета.ИменаРесурсов Цикл
// поиск колонок ресурсов по имени колонке в тексте ячейки либо по наличию расшифровки
Если Ячейка.Текст = ЭлементИмениКолонки.Значение ИЛИ ПоляРасшифровки.Найти(ЭлементИмениКолонки.Значение) <> Неопределено Тогда
Если НомераКолонокРесурсов[ЭлементИмениКолонки.Ключ] = Неопределено Тогда
НомераКолонокРесурсов[ЭлементИмениКолонки.Ключ] = Новый Массив;
КонецЕсли;
Если НомераКолонокРесурсов[ЭлементИмениКолонки.Ключ].Найти(НомерКолонки) = Неопределено Тогда
НомераКолонокРесурсов[ЭлементИмениКолонки.Ключ].Добавить(НомерКолонки);
КонецЕсли;
КонецЕсли;
КонецЦикла;
КонецЦикла;
КонецЦикла;
// проверим все ли номера ресурсов определены
Для Каждого ЭлементНомераКолонки Из НомераКолонокРесурсов Цикл
Если НЕ ЗначениеЗаполнено(ЭлементНомераКолонки.Значение) Тогда
СтруктураОтчета.ТекстОшибкиИнициализации = "Ошибка анализа структуры отчета - не найден ресурс с названием """ + СтруктураОтчета.ИменаРесурсов[ЭлементНомераКолонки.Ключ];
Возврат;
КонецЕсли;
КонецЦикла;
СтруктураОтчета.Вставить("НомераКолонокРесурсов", НомераКолонокРесурсов);
СтруктураОтчета.Вставить("НомерСтрокиОбщихИтоговВерх", НомерПервойСтрокиРасшифровки - 1);
СтруктураОтчета.Вставить("НомерСтрокиОбщихИтоговНиз", ТабличныйДокумент.ВысотаТаблицы);
СтруктураОтчета.Вставить("СтрокиГоризонтальнойКлючевойГруппировки", Новый Соответствие);
СтруктураОтчета.Вставить("СтрокиДопГоризонтальныхГруппировок", Новый Соответствие);
СтруктураОтчета.Вставить("НомераСтрокВсехГоризонтальныхГруппировок", Новый Массив);
СтруктураОтчета.Вставить("ЗначенияВертикальнойКлючевойГруппировки", Неопределено);
Для НомерСтроки = СтруктураОтчета.НомерПервойСтрокиРасшифровки По ТабличныйДокумент.ВысотаТаблицы - 1 Цикл
// определим строки, значения и значения ресурсов по горизонтальной ключевой группировке
ЯчейкаПервойКолонки = ТабличныйДокумент.Область(НомерСтроки, 1);
Если ЯчейкаПервойКолонки.Расшифровка = Неопределено Тогда
Продолжить;
КонецЕсли;
ЭлементРасшифровки = ДанныеРасшифровкиОтчета.Элементы[ЯчейкаПервойКолонки.Расшифровка];
ТекущаяГруппировка = ЭлементРасшифровки.ПолучитьРодителей()[0].ПолучитьПоля()[0];
Если ТекущаяГруппировка.Поле = СтруктураОтчета.ИмяКлючевойГоризонтальнойГруппировки Тогда
// определим значения вертикальной ключевой группировки (однократно - по первой строке горизонтальной ключевой группировки)
Если СтруктураОтчета.ЗначенияВертикальнойКлючевойГруппировки = Неопределено И ЗначениеЗаполнено(СтруктураОтчета.ИмяКлючевойВертикальнойГруппировки) Тогда
СтруктураОтчета.ЗначенияВертикальнойКлючевойГруппировки = Новый Массив;
НомераКолонокЛюбогоРесурса = Неопределено;
Для Каждого ЭлементНомераКолонки Из СтруктураОтчета.НомераКолонокРесурсов Цикл
НомераКолонокЛюбогоРесурса = ЭлементНомераКолонки.Значение;
Прервать;
КонецЦикла;
Если НомераКолонокЛюбогоРесурса <> Неопределено Тогда
Для Каждого НомерКолонки Из НомераКолонокЛюбогоРесурса Цикл
ЯчейкаКолонкиРесурса = ТабличныйДокумент.Область(НомерСтроки, НомерКолонки);
Если ЯчейкаКолонкиРесурса.Расшифровка = Неопределено Тогда
Продолжить;
КонецЕсли;
ЭлементРасшифровки = ДанныеРасшифровкиОтчета.Элементы[ЯчейкаКолонкиРесурса.Расшифровка];
Для Каждого РодительЭлемента Из ЭлементРасшифровки.ПолучитьРодителей() Цикл
Для Каждого ПолеРодителя Из РодительЭлемента.ПолучитьПоля() Цикл
Если ПолеРодителя.Поле = СтруктураОтчета.ИмяКлючевойВертикальнойГруппировки Тогда
СтруктураОтчета.ЗначенияВертикальнойКлючевойГруппировки.Добавить(ПолеРодителя.Значение);
КонецЕсли;
КонецЦикла;
КонецЦикла;
КонецЦикла;
КонецЕсли;
КонецЕсли;
// получим данные по ключевой горизонтальной группировке
ЗначениеКлючевойГоризонтальнойГруппировки = ТекущаяГруппировка.Значение;
ЭтоРедактируемаяГоризонтальнаяГруппировка = СтруктураОтчета.МассивРедактируемыхЗначенийКлючевойГоризонтальнойГруппировки = Неопределено ИЛИ СтруктураОтчета.МассивРедактируемыхЗначенийКлючевойГоризонтальнойГруппировки.Найти(ЗначениеКлючевойГоризонтальнойГруппировки) <> Неопределено;
РесурсыСтрокиОтчета = ПолучитьЗначенияРесурсовСтрокиОтчета(СтруктураОтчета, ТабличныйДокумент, НомерСтроки, ЭтоРедактируемаяГоризонтальнаяГруппировка);
РесурсыСтрокиОтчета.Вставить(СтруктураОтчета.ИмяКлючевойГоризонтальнойГруппировки, ЗначениеКлючевойГоризонтальнойГруппировки);
СтруктураОтчета.СтрокиГоризонтальнойКлючевойГруппировки.Вставить(НомерСтроки, РесурсыСтрокиОтчета);
СтруктураОтчета.НомераСтрокВсехГоризонтальныхГруппировок.Добавить(НомерСтроки);
ИначеЕсли СтруктураОтчета.ДопГоризонтальныеГруппировки <> Неопределено И СтруктураОтчета.ДопГоризонтальныеГруппировки.Свойство(ТекущаяГруппировка.Поле) Тогда
СтруктураОтчета.СтрокиДопГоризонтальныхГруппировок.Вставить(НомерСтроки, Новый Структура(ТекущаяГруппировка.Поле, ТекущаяГруппировка.Значение));
СтруктураОтчета.НомераСтрокВсехГоризонтальныхГруппировок.Добавить(НомерСтроки);
Если СтруктураОтчета.ДопГоризонтальныеГруппировки[ТекущаяГруппировка.Поле] Тогда // если необходим пересчет
// вызов нижестоящего метода требуется исключительно для преобразования ячеек ресурсов с итогами вышестоящих группировок в содержащие значение
ПолучитьЗначенияРесурсовСтрокиОтчета(СтруктураОтчета, ТабличныйДокумент, НомерСтроки);
КонецЕсли;
КонецЕсли;
КонецЦикла;
// преобразуем в значения ресурсы строк с вертикальными итогами
Если СтруктураОтчета.ИспользуютсяОбщиеВертикальныеИтогиВерх Тогда
ПолучитьЗначенияРесурсовСтрокиОтчета(СтруктураОтчета, ТабличныйДокумент, СтруктураОтчета.НомерСтрокиОбщихИтоговВерх);
КонецЕсли;
Если СтруктураОтчета.ИспользуютсяОбщиеВертикальныеИтогиНиз Тогда
ПолучитьЗначенияРесурсовСтрокиОтчета(СтруктураОтчета, ТабличныйДокумент, СтруктураОтчета.НомерСтрокиОбщихИтоговНиз);
КонецЕсли;
СтруктураОтчета.Инициализирована = Истина;
КонецПроцедуры
// выполняет инициализацию структуры отчета, сохраняет ее в параметрах компоновщика настроек и возвращает ссылку на нее для дополнительных донастроек редактируемого отчета
Функция ИнициализироватьСтруктуруОтчета(КомпоновщикНастроек, ИмяКлючевойГоризонтальнойГруппировки, ИмяКлючевойВертикальнойГруппировки = "") Экспорт
СтруктураОтчета = Новый Структура();
СтруктураОтчета.Вставить("Инициализирована", Ложь); // была ли выполнена инициализация структуры отчета после компоновки
СтруктураОтчета.Вставить("ДанныеИзменены", Ложь); // были ли изменены данные пользователем
СтруктураОтчета.Вставить("ТекстОшибкиИнициализации", ""); // будет содержать описание ошибки инициализации, если произошла ошибка инициализации
СтруктураОтчета.Вставить("ИмяКлючевойГоризонтальнойГруппировки", ИмяКлючевойГоризонтальнойГруппировки);
СтруктураОтчета.Вставить("ИмяКлючевойВертикальнойГруппировки", ИмяКлючевойВертикальнойГруппировки);
СтруктураОтчета.Вставить("ДопГоризонтальныеГруппировки", Неопределено); // сюда при необходимоти необходимо передать структуру, ключи которой содержат имена доп-группировок, а значения - необходимость пересчета итогов по ним
СтруктураОтчета.Вставить("ИспользуютсяОбщиеВертикальныеИтогиВерх", Ложь); // признак использования верхних вертикальных итогов (изменить при необходимости)
СтруктураОтчета.Вставить("ИспользуютсяОбщиеВертикальныеИтогиНиз", Истина); // признак использования нижних вертикальных итогов (изменить при необходимости)
СтруктураОтчета.Вставить("ИменаРесурсов", Новый Структура()); // служебное (заполняется при вызове ДобавитьРесурсВСтруктуруОтчета)
СтруктураОтчета.Вставить("ТипыРесурсов", Новый Структура()); // служебное (заполняется при вызове ДобавитьРесурсВСтруктуруОтчета)
СтруктураОтчета.Вставить("ФорматыРесурсов", Новый Структура()); // служебное (заполняется при вызове ДобавитьРесурсВСтруктуруОтчета)
СтруктураОтчета.Вставить("РесурсыСПересчетомИтогов", Новый Структура()); // служебное (заполняется при вызове ДобавитьРесурсВСтруктуруОтчета)
СтруктураОтчета.Вставить("РедактируемыеРесурсы", Новый Структура()); // служебное (заполняется при вызове ДобавитьРесурсВСтруктуруОтчета)
СтруктураОтчета.Вставить("МассивРедактируемыхЗначенийКлючевойГоризонтальнойГруппировки", Неопределено); // необходим в случаях, когда не по всем значениям ключевой горизонтальной группировки необходимо разрешить редактировать данные
СтруктураОтчета.Вставить("МассивРедактируемыхЗначенийКлючевойВертикальнойГруппировки", Неопределено); // необходим в случаях, когда не по всем значениям ключевой вертикальной группировки необходимо разрешить редактировать данные
СтруктураОтчета.Вставить("ЦветРедактированныхЯчеек", WebЦвета.СветлоЖелтый);// цвет отображения отредактированных ячеек (изменить при необходимости)
КомпоновщикНастроек.Настройки.ДополнительныеСвойства.Вставить("СтруктураОтчета", СтруктураОтчета);
Возврат СтруктураОтчета;
КонецФункции
// добавляет в структуру отчета информацию о новом ресурсе
Процедура ДобавитьРесурсВСтруктуруОтчета(КомпоновщикНастроек, Имя, Тип, Формат, Представление, Пересчитывается = Ложь, Редактируется = Ложь) Экспорт
СтруктураОтчета = КомпоновщикНастроек.Настройки.ДополнительныеСвойства.СтруктураОтчета;
СтруктураОтчета.ИменаРесурсов.Вставить(Имя, Представление);
СтруктураОтчета.ТипыРесурсов.Вставить(Имя, Тип);
СтруктураОтчета.ФорматыРесурсов.Вставить(Имя, Формат);
Если Пересчитывается Тогда
СтруктураОтчета.РесурсыСПересчетомИтогов.Вставить(Имя);
КонецЕсли;
Если Редактируется Тогда
СтруктураОтчета.РедактируемыеРесурсы.Вставить(Имя);
КонецЕсли;
КонецПроцедуры