Как реализовать ручной ввод данных пользователем в отчет 1С и динамический пересчет формул

Программист 1С v8.3 (Обычные формы) Управленческий учет IT и автоматизация бизнеса
← На главную

В практике разработки на платформе 1С программисты часто сталкиваются с требованиями пользователей перенести логику сложных таблиц Excel непосредственно в интерфейс информационной системы. Типичный сценарий: отчет выводит множество колонок, рассчитанных по сложным запросам и алгоритмам, но одну или несколько колонок пользователь должен иметь возможность заполнять или корректировать вручную. При этом система должна мгновенно пересчитывать итоговые показатели строки "на лету", а при повторном формировании отчета введенные пользователем данные не должны бесследно исчезать.

В этой статье мы подробно разберем, как решить эту задачу на платформе 1С для обычных форм. Мы выясним причину распространенных ошибок при попытке редактирования ячеек табличного документа, научимся работать с событиями интерактивного изменения данных и рассмотрим два надежных архитектурных подхода к сохранению пользовательского ввода.

Почему возникает ошибка «Поле объекта недоступно для чтения» и как настроить ячейки табличного документа

При попытке обработать изменение ячеек в процедуре ТабДокПриИзмененииСодержимогоОбласти разработчики часто сталкиваются с тем, что отладчик выдает ошибку, а свойства ячеек оказываются недоступными. Проанализируем ситуацию. По умолчанию ячейки табличного документа предназначены исключительно для вывода текстовой информации. Свойство СодержитЗначение для них установлено в значение Ложь. В таком режиме любая попытка прочитать или записать свойство Значение ячейки вызывает системное исключение.

Чтобы превратить обычную ячейку табличного документа в интерактивное поле ввода, необходимо правильно настроить свойства ячейки в макете отчета или сделать это программно при выводе строки. Выполним следующие настройки:

  1. Установим свойство СодержитЗначение в Истина. Это ключевой шаг, который сообщает платформе, что ячейка теперь является типизированным полем ввода.
  2. Зададим ТипЗначения. Нам необходимо явно указать тип данных (например, число с нужной точностью и неотрицательным знаком), чтобы пользователь не мог ввести некорректные символы.
  3. Снимем защиту с редактируемой колонки. Свойство Защита для редактируемой колонки (в нашем примере это «Товар в пути») должно быть установлено в Ложь.
  4. Включим защиту для всех остальных областей. Все остальные 18 колонок макета, которые рассчитываются автоматически, должны иметь свойство Защита = Истина. Это убережет отчет от случайного повреждения формул и запросных данных пользователем.
  5. Настроим сам табличный документ на форме. Элемент управления ТабличныйДокумент, отображаемый на форме, по умолчанию имеет свойство ТолькоПросмотр = Истина. Для реализации нашей задачи мы обязаны программно или в свойствах элемента формы установить ТолькоПросмотр = Ложь. Не беспокойтесь о сохранности остальных ячеек: благодаря точечной настройке свойства Защита в самом макете, пользователь сможет кликнуть и изменить данные только в разрешенной колонке.

Рассмотрим, как выполнить эти настройки программно в цикле вывода строк отчета, чтобы не перегружать макет жесткими ограничениями:


// Пример программной настройки ячейки при выводе строки
ОбластьТоварВПути = ДанныеСтроки.Область("R" + НомерСтроки + "C10"); // Предположим, 10-я колонка
ОбластьТоварВПути.СодержитЗначение = Истина;
ОбластьТоварВПути.ТипЗначения = Новый ОписаниеТипов("Число", Новый КвалификаторыЧисла(15, 3, ДопустимыйЗнак.Неотрицательный));
ОбластьТоварВПути.Защита = Ложь;

Реализация динамического пересчета через событие изменения содержимого области

Для отслеживания действий пользователя мы задействуем стандартное событие табличного документа формы ПриИзмененииСодержимогоОбласти. Разберем по шагам алгоритм работы этого обработчика. Нам нужно определить координаты измененной ячейки, проверить, относится ли она к нашей редактируемой колонке «Товар в пути», считать введенное число, получить остальные слагаемые из этой же строки и перезаписать итоговое значение «Остаток на конец».

Посмотрим на пример практической реализации данного обработчика:


Процедура ТабДокПриИзмененииСодержимогоОбласти(Элемент, Область)
    
    // Определяем координаты измененной ячейки
    СтрокаИзменения = Область.Верх;
    КолонкаИзменения = Область.Лево;
    
    // Предположим, что колонка "Товар в пути" имеет индекс 10
    НомерКолонкиТоварВПути = 10;
    
    Если КолонкаИзменения = НомерКолонкиТоварВПути Тогда
        
        // Получаем введенное пользователем значение
        ВведенноеЗначение = Область.Значение;
        Если ВведенноеЗначение = Неопределено Тогда
            ВведенноеЗначение = 0;
        КонецЕсли;
        
        // Определяем номера колонок для расчета
        // Формула: ОстатокНаКонец = (ОстатокНаНачало + Приход + ТоварВПути) - Продажа
        КолонкаОстатокНаНачало = 5;
        КолонкаПриход          = 6;
        КолонкаПродажа         = 8;
        КолонкаОстатокНаКонец  = 12;
        
        // Считываем значения из других ячеек этой же строки
        ОстатокНаНачало = Число(Элемент.Область(СтрокаИзменения, КолонкаОстатокНаНачало).Текст);
        Приход          = Число(Элемент.Область(СтрокаИзменения, КолонкаПриход).Текст);
        Продажа         = Число(Элемент.Область(СтрокаИзменения, КолонкаПродажа).Текст);
        
        // Выполняем расчет по формуле
        НовыйОстатокНаКонец = (ОстатокНаНачало + Приход + ВведенноеЗначение) - Продажа;
        
        // Записываем новое расчетное значение в ячейку "Остаток на конец"
        ЯчейкаИтога = Элемент.Область(СтрокаИзменения, КолонкаОстатокНаКонец);
        ЯчейкаИтога.Текст = Формат(НовыйОстатокНаКонец, "ЧДЦ=3");
        
        // Сохраняем введенное значение во временном буфере формы, 
        // чтобы оно не потерялось при переформировании
        СохранитьВведенноеЗначениеВБуфер(СтрокаИзменения, ВведенноеЗначение, Элемент);
        
    КонецЕсли;
    
КонецПроцедуры

Как сделать так, чтобы введенные данные не затирались при переформировании отчета

Основная сложность, о которой предупреждали участники обсуждения на форуме, заключается в том, что при повторном нажатии пользователем кнопки «Сформировать» табличный документ полностью очищается, запрос выполняется заново, и все введенные вручную данные будут утеряны. Нам необходимо спроектировать механизм сохранения этих корректировок. Рассмотрим два эффективных архитектурных варианта решения этой проблемы.

Вариант А: Использование временной таблицы значений на форме (без записи в базу данных)

Этот вариант идеально подходит, если пользователю необходимо оперативно скорректировать цифры «здесь и сейчас» для быстрой выгрузки или печати отчета руководству, и эти изменения не требуется постоянно хранить в информационной базе.

Разберем по шагам логику этого подхода:

  1. На форме отчета создаем новый реквизит ВведенныеКорректировки с типом ТаблицаЗначений. Добавим в эту таблицу две колонки: КлючСтроки (тип может быть ссылкой на справочник Номенклатура или любым другим уникальным идентификатором строки) и Количество (тип Число).
  2. При первоначальном выводе строк отчета в ячейку «Товар в пути» мы обязательно записываем уникальный идентификатор строки в свойство Расшифровка. Это позволит нам в любой момент однозначно сопоставить ячейку таблицы с конкретным товаром.
  3. В процедуре обработки изменения области мы считываем ключ строки из расшифровки измененной ячейки и записываем (или обновляем) значение в нашей таблице значений формы ВведенныеКорректировки.
  4. При повторном формировании отчета, в цикле обхода результата запроса перед выводом строки в табличный документ, мы выполняем поиск по ключу в таблице ВведенныеКорректировки. Если запись найдена, мы подставляем сохраненное число в колонку «Товар в пути» и сразу же программно рассчитываем итоговый остаток строки.

Рассмотрим пример кода для реализации этого варианта:


// Процедура записи измененных данных в таблицу на форме
Процедура СохранитьВведенноеЗначениеВБуфер(НомерСтроки, Значение, ТабДок)
    
    // Получаем ключ строки из расшифровки измененной ячейки
    КлючСтроки = ТабДок.Область(НомерСтроки, 10).Расшифровка;
    
    Если ЗначениеЗаполнено(КлючСтроки) Тогда
        
        // Ищем строку в нашей таблице хранения
        ПараметрыПоиска = Новый Структура("КлючСтроки", КлючСтроки);
        НайденныеСтроки = ВведенныеКорректировки.НайтиСтроки(ПараметрыПоиска);
        
        Если НайденныеСтроки.Количество() > 0 Тогда
            СтрокаБуфера = НайденныеСтроки[0];
        Иначе
            СтрокаБуфера = ВведенныеКорректировки.Добавить();
            СтрокаБуфера.КлючСтроки = КлючСтроки;
        КонецЕсли;
        
        СтрокаБуфера.Количество = Значение;
        
    КонецЕсли;
    
КонецПроцедуры

Теперь проанализируем, как эти данные будут подставляться обратно в отчет при его переформировании кнопкой «Сформировать»:


// Фрагмент кода заполнения отчета в цикле обхода выборки запроса
Пока ВыборкаДетали.Следующий() Цикл
    
    // Выводим стандартные данные строки
    ТабДок.Вывести(ОбластьСтрока);
    НомерТекущейСтроки = ТабДок.ВысотаТаблицы;
    
    // Заполняем расшифровку для последующей идентификации ячейки
    ЯчейкаВвода = ТабДок.Область(НомерТекущейСтроки, 10);
    ЯчейкаВвода.Расшифровка = ВыборкаДетали.Номенклатура;
    ЯчейкаВвода.СодержитЗначение = Истина;
    ЯчейкаВвода.ТипЗначения = Новый ОписаниеТипов("Число", Новый КвалификаторыЧисла(15, 3));
    ЯчейкаВвода.Защита = Ложь;
    
    // Проверяем, вносил ли пользователь изменения ранее
    СтрокаПоиска = ВведенныеКорректировки.Найти(ВыборкаДетали.Номенклатура, "КлючСтроки");
    
    Если СтрокаПоиска <> Неопределено Тогда
        // Подставляем сохраненное пользователем значение
        ЯчейкаВвода.Значение = СтрокаПоиска.Количество;
        ТоварВПути = СтрокаПоиска.Количество;
    Иначе
        // Значение по умолчанию
        ЯчейкаВвода.Значение = 0;
        ТоварВПути = 0;
    EndIf;
    
    // Рассчитываем итоговую колонку с учетом ручных корректировок
    ОстатокНаНачало = ВыборкаДетали.ОстатокНаНачало;
    Приход          = ВыборкаДетали.Приход;
    Продажа         = ВыборкаДетали.Продажа;
    
    ОстатокНаКонец = (ОстатокНаНачало + Приход + ТоварВПути) - Продажа;
    
    // Записываем итог
    ТабДок.Область(НомерТекущейСтроки, 12).Текст = Формат(ОстатокНаКонец, "ЧДЦ=3");
    
КонецЦикла;

Вариант Б: Использование регистра сведений (с записью в базу данных)

Если введенные пользователем данные по товарам в пути должны сохраняться в системе перманентно (например, чтобы другие менеджеры или директор при открытии отчета на следующий день видели эти же цифры), использование временных таблиц формы не поможет. В этом случае нам необходимо организовать хранение корректировок в базе данных.

Наилучшим техническим решением будет создание простого независимого регистра сведений с именем КорректировкиТоваровВПути. В качестве измерений определим Номенклатура (и, если требуется аналитика, Склад или Организация), а ресурсом сделаем Количество с типом Число.

При таком подходе логика работы программы изменится в лучшую сторону:

  1. В событии ПриИзмененииСодержимогоОбласти мы сразу записываем введенное пользователем число в регистр сведений, используя менеджер записи:

// Запись изменений напрямую в базу данных
Процедура СохранитьВведенноеЗначениеВРегистр(НоменклатураСсылка, Значение)
    
    МенеджерЗаписи = РегистрыСведений.КорректировкиТоваровВПути.СоздатьМенеджерЗаписи();
    МенеджерЗаписи.Номенклатура = НоменклатураСсылка;
    
    Если Значение = 0 Тогда
        // Если пользователь очистил ячейку, удаляем запись из регистра
        МенеджерЗаписи.Удалить();
    Иначе
        // Записываем новое значение
        МенеджерЗаписи.Количество = Значение;
        МенеджерЗаписи.Записать(Истина);
    КонецЕсли;
    
КонецПроцедуры
  1. При формировании отчета нам больше не нужно вручную осуществлять поиск по таблицам на форме. Мы просто модифицируем исходный запрос отчета, добавив ЛЕВОЕ СОЕДИНЕНИЕ нашей таблицы данных с виртуальной таблицей среза последних или просто физической таблицей регистра сведений КорректировкиТоваровВПути по полю Номенклатура.
  2. В результате колонка «Товар в пути» будет сразу заполняться актуальными значениями прямо из запроса, а расчет итоговых остатков выполнится автоматически по стандартному коду вывода отчета. Этот вариант является наиболее надежным, методологически верным и простым в долгосрочном сопровождении.

Полезные рекомендации по улучшению интерфейса и юзабилити отчета

Чтобы пользователи могли комфортно работать с интерактивным отчетом, рекомендуем применить несколько полезных интерфейсных приемов:

Мы подробно разобрали, как превратить статичный табличный документ 1С в удобный интерактивный инструмент с возможностью ручного ввода данных. Комбинируя правильную настройку свойств ячеек, обработку событий изменения содержимого и надежные механизмы сохранения данных через временные буферы или регистры сведений, вы сможете решать любые нестандартные задачи по автоматизации рабочих мест ваших пользователей.

← На главную