При разработке отчетов на базе системы компоновки данных (СКД) часто возникает бизнес-требование: позволить пользователю редактировать определенную колонку прямо в сформированном отчете. Например, это может быть колонка «Остаток для заказа», «Примечание» или «Плановые показатели». По умолчанию все ячейки отчета СКД защищены от изменений. Простое снятие галочки защиты в свойствах ячейки не дает нужного результата.
В этой статье мы подробно разберем, почему возникает проблема редактирования, как программно снять защиту с нужной колонки и как организовать полноценное сохранение введенных пользователем данных в базу данных, чтобы они не терялись при повторном формировании отчета.
Многие разработчики пытаются решить задачу «в лоб»: находят область ячеек и устанавливают свойство Защита = Ложь. Однако пользователь все равно не может ввести данные. Выясним причину такого поведения платформы. Дело в том, что возможность редактирования ячеек в отчете зависит от триады взаимосвязанных свойств:
ДокументРезультат) включено свойство Защита = Истина, то редактирование любых ячеек будет заблокировано на глобальном уровне, независимо от индивидуальных настроек этих ячеек.Истина. При таком режиме редактировать можно только те ячейки, у которых свойство Защита сброшено в значение Ложь.Редактирование. Если оно равно Ложь (что является стандартом для отчетов), то интерактивный ввод символов с клавиатуры будет полностью заблокирован.Таким образом, для реализации ввода нам необходимо программно управлять всеми тремя параметрами.
Чтобы управлять свойствами сформированного отчета, нам потребуется отказаться от стандартного вывода СКД и выполнить компоновку программно. Для этого откроем модуль объекта отчета и создадим предопределенную процедуру ПриКомпоновкеРезультата.
Рассмотрим подробнее программный код, который выполняет вывод отчета, находит нужную колонку по имени заголовка, снимает с нее защиту и назначает корректный тип данных для ввода чисел:
Процедура ПриКомпоновкеРезультата(ДокументРезультат, ДанныеРасшифровки, СтандартнаяОбработка)
// Отменяем стандартный вывод отчета платформой
СтандартнаяОбработка = Ложь;
// Инициализируем макет и процессор компоновки данных
КомпоновщикМакета = Новый КомпоновщикМакетаКомпоновкиДанных;
Настройки = КомпоновщикНастроек.ПолучитьНастройки();
МакетКомпоновкиДанных = КомпоновщикМакета.Выполнить(СхемаКомпоновкиДанных, Настройки, ДанныеРасшифровки);
ПроцессорКомпоновкиДанных = Новый ПроцессорКомпоновкиДанных;
ПроцессорКомпоновкиДанных.Инициализировать(МакетКомпоновкиДанных, , ДанныеРасшифровки, Истина);
// Выводим результат в табличный документ
ПроцессорВывода = Новый ПроцессорВыводаРезультатаКомпоновкиДанныхВТабличныйДокумент;
ПроцессорВывода.УстановитьДокумент(ДокументРезультат);
ПроцессорВывода.Вывести(ПроцессорКомпоновкиДанных);
// Настраиваем глобальные свойства табличного документа
ДокументРезультат.Защита = Ложь; // Разрешаем изменять документ в целом
ДокументРезультат.ТолькоПросмотр = Истина; // Оставляем режим просмотра для защищенных ячеек
// Находим колонку "Остаток" по тексту ее заголовка
ОбластьЗаголовка = ДокументРезультат.НайтиТекст("Остаток");
Если ТипЗнч(ОбластьЗаголовка) <> Тип("Неопределено") Тогда
НомерКолонки = ОбластьЗаголовка.Лево;
// Получаем область всей колонки (сверху донизу) по ее индексу
ОбластьКолонки = ДокументРезультат.Область( , НомерКолонки, , НомерКолонки);
// Снимаем защиту с ячеек этой колонки
ОбластьКолонки.Защита = Ложь;
// Настраиваем типизацию ячеек, чтобы платформа ожидала ввод чисел
ОбластьКолонки.СодержитЗначение = Истина;
ОбластьКолонки.ТипЗначения = Новый ОписаниеТипов("Число", Новый КвалификаторыЧисла(15, 2));
КонецЕсли;
КонецПроцедуры
Важный нюанс: Метод ДокументРезультат.НайтиТекст("Остаток") ищет первое вхождение слова в табличном документе. Убедитесь, что заголовок вашей редактируемой колонки уникален и не дублируется в других частях отчета (например, в шапке или параметрах).
Если ваш отчет работает в конфигурации, использующей Библиотеку стандартных подсистем (БСП) — например, в «Управлении торговлей» или «ERP Управление предприятием», то стандартная форма отчета принудительно перезаписывает свойства отображаемого табличного документа после завершения вывода. Она возвращает элементу формы режим Редактирование = Ложь.
Чтобы обойти это ограничение, нам необходимо создать собственную форму отчета и в ее модуле принудительно включить доступ к редактированию. Разберем по шагам, как это сделать:
ПриОткрытии, если отчет формируется автоматически).
&НаКлиенте
Процедура ПослеВыводаОтчетаКлиент()
// "Результат" — это стандартное имя реквизита табличного документа на форме отчета
Элементы.Результат.Редактирование = Истина;
КонецПроцедуры
Пользователи часто забывают, что отчет — это временная таблица, отображающая срез данных из базы. Любое повторное нажатие на кнопку «Сформировать» очистит табличный документ и сотрет все ручные изменения. Нам необходимо организовать постоянное хранение введенных остатков.
Проанализируем ситуацию и реализуем полноценную систему сохранения по следующему алгоритму:
Создайте в конфигураторе новый независимый регистр сведений. Назовем его РучныеОстаткиНоменклатуры. Задайте следующую структуру:
Номенклатура (тип СправочникСсылка.Номенклатура)Остаток (тип Число, длина 15, точность 2)Чтобы система понимала, к какой именно номенклатуре относится строка, в которой пользователь изменил остаток, у ячеек этой строки должно быть заполнено свойство расшифровки. Убедитесь, что в настройках СКД на вкладке «Наборы данных» для поля Номенклатура или в группировке детальных записей настроено поле расшифровки.
Для элемента формы Результат (Поле табличного документа) назначим обработчик события ПриИзмененииСодержимогоОбласти. Этот обработчик будет перехватывать ввод данных пользователем, определять измененный товар и отправлять данные для записи в регистр сведений.
Посмотрим на пример реализации клиент-серверного взаимодействия в модуле формы отчета:
&НаКлиенте
Процедура РезультатПриИзмененииСодержимогоОбласти(Элемент, Область)
// Проверяем, что изменение произошло именно в нашей редактируемой колонке.
// Для этого можно завязаться на имя области или проверить заголовок колонки.
Если Область.Лево = НомерРедактируемойКолонки Тогда
СтрокаТаблицы = Область.Верх;
// Получаем расшифровку из ячейки с Номенклатурой в этой же строке
// Допустим, колонка с Номенклатурой имеет индекс 1
ЯчейкаНоменклатуры = Элементы.Результат.Область(СтрокаТаблицы, 1);
ИдентификаторРасшифровки = ЯчейкаНоменклатуры.ПараметрРасшифровки;
Если ЗначениеЗаполнено(ИдентификаторРасшифровки) Тогда
НоменклатураСсылка = ПолучитьНоменклатуруИзРасшифровки(ИдентификаторРасшифровки);
НовоеЗначениеОстатка = Область.Значение;
ЗаписатьОстатокВБазуНаСервере(НоменклатураСсылка, НовоеЗначениеОстатка);
КонецЕсли;
КонецЕсли;
КонецПроцедуры
&НаСервереБезКонтекста
Функция ПолучитьНоменклатуруИзРасшифровки(ИдентификаторРасшифровки)
// Извлекаем данные расшифровки, сохраненные при выводе отчета
Данные = ПолучитьИзВременногоХранилища(АдресДанныхРасшифровки);
// Находим значение поля "Номенклатура" по идентификатору
Если Данные <> Неопределено Тогда
Возврат Данные.Элементы[ИдентификаторРасшифровки].ПолучитьПоля()[0].Значение;
КонецЕсли;
Возврат Справочники.Номенклатура.ПустаяСсылка();
КонецФункции
&НаСервереБезКонтекста
Процедура ЗаписатьОстатокВБазуНаСервере(НоменклатураСсылка, НовоеЗначениеОстатка)
// Записываем введенный остаток в наш регистр сведений
НаборЗаписей = РегистрыСведений.РучныеОстаткиНоменклатуры.СоздатьНаборЗаписей();
НаборЗаписей.Отбор.Номенклатура.Установить(НоменклатураСсылка);
НоваяЗапись = НаборЗаписей.Добавить();
НоваяЗапись.Номенклатура = НоменклатураСсылка;
НоваяЗапись.Остаток = НовоеЗначениеОстатка;
НаборЗаписей.Записать();
КонецПроцедуры
Последний шаг — сделать так, чтобы при каждом открытии и формировании отчета СКД автоматически считывала ранее записанные остатки из регистра сведений. Для этого достаточно изменить запрос основного набора данных в схеме компоновки данных, добавив левое соединение с регистром сведений:
ВЫБРАТЬ
Продажи.Номенклатура КАК Номенклатура,
Продажи.КоличествоОборот КАК Продажи,
ЕСТЬNULL(РучныеОстатки.Остаток, 0) КАК Остаток
ИЗ
РегистрНакопления.Продажи.Обороты КАК Продажи
ЛЕВОЕ СОЕДИНЕНИЕ РегистрСведений.РучныеОстаткиНоменклатуры КАК РучныеОстатки
ПО Продажи.Номенклатура = РучныеОстатки.Номенклатура
Теперь система работает циклично и надежно: отчет извлекает сохраненные остатки из базы, выводит их в разблокированную колонку, а любые новые изменения пользователя мгновенно записываются обратно в базу данных. Задача решена полностью и корректно!