Приветствуем вас, коллеги! Сегодня мы детально разберем одну из классических и крайне важных задач при разработке в среде 1С:Предприятие 8 — программное создание и заполнение документов на основании других объектов. С этой задачей регулярно сталкиваются как начинающие разработчики, так и опытные специалисты, выполняющие поддержку исторических систем на обычных формах.
В этой статье мы подробно проанализируем ситуацию, когда пользователю при создании нового документа из формы списка необходимо предложить выбрать документ-источник (например, «Заказ покупателя»), а затем автоматически перенести все сопоставимые реквизиты шапки и табличной части в новый объект. Мы разберем как простое интерактивное решение, так и методологически верный архитектурный подход, соответствующий стандартам разработки фирмы «1С».
Прежде чем переходить к написанию кода, давайте разберем саму суть проблемы и типичные ошибки проектирования, которые допускают программисты при решении этой задачи.
Очень часто разработчики пытаются решить задачу «в лоб», размещая весь код интерактивного выбора и последующего заполнения непосредственно в модуле формы нового документа, в обработчике события ПриОткрытии или ПередОткрытием. Рассмотрим пример такого кода, который часто можно встретить на практике (именно этот вариант изначально пытался реализовать автор темы):
Процедура ПриОткрытии()
// Пытаемся спросить пользователя, нужно ли заполнение
Если Вопрос("Создать на основании заказа?", РежимДиалогаВопрос.ДаНет, 10) = КодВозвратаДиалога.Да Тогда
ДокОснование = Документы.ЗаказПокупателя.ПустаяСсылка();
// Открываем модальное окно выбора документа
Если ВвестиЗначение(ДокОснование, "Выберите документ-основание") Тогда
// Начинаем прямо в форме заполнять реквизиты создаваемого объекта
ЭтотОбъект.Номер = ДокОснование.НомерЗаказа;
ЭтотОбъект.Дата = ТекущаяДата();
ЭтотОбъект.Ответственный = ДокОснование.Ответственный;
// Обходим табличную часть документа-источника
Для Каждого Стр Из ДокОснование.СписокНоменклатуры Цикл
НоваяСтрока = ЭтотОбъект.Товары.Добавить();
НоваяСтрока.Номенклатура = Стр.Наименование;
НоваяСтрока.Количество = Стр.Количество;
КонецЦикла;
Иначе
// Если пользователь отказался от выбора, закрываем форму
Отказ = Истина;
КонецЕсли;
КонецЕсли;
КонецПроцедуры
Несмотря на то, что данный код является полностью рабочим и действительно решает локальную задачу пользователя в пользовательском интерфейсе, использовать его на реальных внедренных конфигурациях категорически не рекомендуется. Выясним причину, почему такое решение считается грубым антипаттерном в мире 1С.
Разберем детально, к каким негативным последствиям приводит написание логики заполнения внутри модуля формы:
ВвестиЗначение или модальное открытие форм выбора блокирует поток выполнения программы. В современных реалиях модальность полностью запрещена, так как она не поддерживается в веб-клиентах и на мобильных платформах.ПриОткрытии срабатывает в тот момент, когда объект в памяти уже частично создан, и для него выделены ресурсы. Прерывание процесса открытия через Отказ = Истина после диалогового окна может приводить к избыточной нагрузке на СУБД и накоплению временных объектов в памяти.Как же поступить правильно? Нам необходимо разделить задачу на два независимых этапа. На первом этапе мы перехватываем интерактивное нажатие кнопки «Добавить» в форме списка и предлагаем пользователю выбрать основание. На втором этапе мы передаем выбранное основание в модуль самого создаваемого объекта и выполняем заполнение на уровне бизнес-логики СУБД.
Рассмотрим по шагам правильный алгоритм реализации данной задачи в обычных формах 1С:Предприятие 8.
Вместо того чтобы изменять поведение формы самого документа при ее открытии, мы перехватим процесс добавления нового элемента непосредственно в форме списка (или форме журнала документов), откуда пользователь инициирует создание.
Для этого откроем форму списка нашего документа, выделим табличное поле, отображающее список документов, и создадим обработчик события ПередНачаломДобавления. Напишем в нем следующий код:
Процедура СписокПередНачаломДобавления(Элемент, Отказ, Копирование, Родитель, ЭтоГруппа)
// Если пользователь копирует существующую строку списка, не вмешиваемся
Если Копирование Тогда
Возврат;
КонецЕсли;
// 1. Отклоняем стандартное интерактивное добавление документа платформой
Отказ = Истина;
// 2. Предлагаем пользователю выбрать документ-источник
СсылкаОснование = Документы.ЗаказПокупателя.ПустаяСсылка();
Если ВвестиЗначение(СсылкаОснование, "Выберите заказ-основание для нового документа") Тогда
// 3. Программно создаем новый документ в памяти сервера
НовыйДокумент = Документы.НашНовыйДокумент.СоздатьДокумент();
// 4. Заполняем реквизиты на основании выбранного заказа.
// Вызов метода Заполнить() автоматически перенаправит выполнение в модуль объекта
НовыйДокумент.Заполнить(СсылкаОснование);
// 5. Получаем форму нового документа для интерактивного показа пользователю
ФормаДокумента = НовыйДокумент.ПолучитьФорму();
// Устанавливаем связь параметров для корректного отображения структуры подчиненности
ФормаДокумента.ПараметрОснование = СсылкаОснование;
// Открываем заполненную форму на экране
ФормаДокумента.Открыть();
КонецЕсли;
КонецПроцедуры
Проанализируем этот код. Мы установили свойство Отказ = Истина;, чтобы стандартный механизм создания пустого документа не запускался. Вместо этого мы интерактивно получили ссылку на документ-основание, программно создали объект с помощью метода СоздатьДокумент() и вызвали метод Заполнить(). Метод Заполнить() является ключевым: он автоматически передает управление в предопределенную процедуру модуля объекта.
Теперь перейдем в модуль объекта нашего создаваемого документа. Здесь нам необходимо использовать стандартное предопределенное событие ОбработкаЗаполнения. Именно внутри этого обработчика должна располагаться вся логика сопоставления реквизитов и заполнения табличных частей.
Давайте посмотрим на пример реализации процедуры ОбработкаЗаполнения:
Процедура ОбработкаЗаполнения(ДанныеЗаполнения, СтандартнаяОбработка)
// Проверяем тип пришедших данных заполнения
Если ТипЗнч(ДанныеЗаполнения) = Тип("ДокументСсылка.ЗаказПокупателя") Тогда
// Сценарий 1. Быстрое заполнение реквизитов с одинаковыми именами
ЗаполнитьЗначенияСвойств(ЭтотОбъект, ДанныеЗаполнения);
// Сценарий 2. Если имена реквизитов различаются, сопоставляем их вручную
ЭтотОбъект.Ответственный = ПараметрыСеанса.ТекущийПользователь;
ЭтотОбъект.Дата = ТекущаяДата();
// Сценарий 3. Копирование и конвертация табличной части
// Очищаем табличную часть перед заполнением (на случай повторного вызова)
ЭтотОбъект.Товары.Очистить();
Для Каждого СтрокаИсточника Из ДанныеЗаполнения.Товары Цикл
НоваяСтрока = ЭтотОбъект.Товары.Добавить();
// Заполняем реквизиты строки табличной части
НоваяСтрока.Номенклатура = СтрокаИсточника.Номенклатура;
НоваяСтрока.Количество = СтрокаИсточника.Количество;
НоваяСтрока.Цена = СтрокаИсточника.Цена;
НоваяСтрока.Сумма = СтрокаИсточника.Сумма;
КонецЦикла;
КонецЕсли;
КонецПроцедуры
Рассмотрим подробнее преимущества использования встроенной функции ЗаполнитьЗначенияСвойств(). Этот метод выполняет сопоставление свойств объектов по их именам. Если в документе-источнике и документе-приемнике есть одинаковые реквизиты шапки (например, Организация, Контрагент, ДоговорКонтрагента), то они автоматически скопируются одной строчкой кода. Это избавляет от написания десятков строк рутинного присвоения.
При заполнении табличных частей возможны два сценария:
ЭтотОбъект.Товары.Загрузить(ДанныеЗаполнения.Товары.Выгрузить());
Такой подход работает молниеносно на больших объемах данных, так как выполняется платформенными механизмами без явного обхода в цикле на встроенном языке.
Материал, а в приемнике — Номенклатура), либо требуется дополнительный расчет данных в процессе переноса. В этом случае обязательно используйте цикл Для Каждого, который мы подробно описали на Шаге 2.Если в перспективе планируется перевод вашей конфигурации на современную платформу 1С:Предприятие 8.3 с использованием управляемых форм, то описанная выше архитектура разделения логики сослужит вам отличную службу. В управляемом приложении вам даже не потребуется перехватывать событие формы списка!
Платформа 1С на управляемых формах делает этот процесс полностью автоматическим на уровне декларативного описания. Достаточно в свойствах вашего документа на вкладке «Ввод на основании» указать документ-источник. После этого платформа сама сгенерирует кнопку на панели навигации формы списка, а при нажатии на нее создаст объект на сервере, вызовет обработчик ОбработкаЗаполнения и откроет готовую форму клиенту.
Если же вам потребуется открыть форму нового документа с заполнением программно на управляемых формах, вы сможете сделать это безопасным бесконфликтным способом без модальных окон, используя структуру параметров формы:
&НаКлиенте
Процедура СоздатьНаОснованииУФ(СсылкаНаЗаказ)
ПараметрыФормы = Новый Структура("Основание", СсылкаНаЗаказ);
ОткрытьФорму("Документ.НашНовыйДокумент.ФормаОбъекта", ПараметрыФормы);
КонецПроцедуры
В этом случае платформа самостоятельно возьмет на себя заботу по вызову процедуры ОбработкаЗаполнения на стороне сервера и передаст туда переданную в параметре ссылку.
Мы подробно проанализировали подходы к заполнению документов на основании в среде 1С. Подводя итог нашей совместной работе, выделим ключевые правила:
ПриОткрытии, ПередОткрытием). Модуль формы предназначен исключительно для управления элементами интерфейса.ОбработкаЗаполнения модуля объекта.ПередНачаломДобавления для интерактивного опроса пользователя и программного вызова метода Заполнить().ЗаполнитьЗначенияСвойств() для быстрого копирования совпадающих реквизитов, чтобы сделать ваш код лаконичным и легко читаемым.Применяя эти простые архитектурные правила, вы гарантированно обезопасите свою конфигурацию от труднодоступных ошибок, повысите общую производительность системы и обеспечите простоту ее дальнейшей поддержки и обновления!