Как программно создать документ в 1С:ERP 2.5, используя метод Заполнить и структуру для минимизации ошибок?

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

В процессе разработки обработок и интеграций в 1С часто возникает необходимость программного создания документов. При этом важно не только заполнить видимые реквизиты, но и корректно инициализировать служебные поля, а также обеспечить срабатывание всей логики, заложенной в документ разработчиками конфигурации. Стандартный подход, когда мы напрямую присваиваем значения реквизитам объекта документа, может приводить к ошибкам, особенно если не все реквизиты учтены или не соблюдена последовательность их заполнения. Рассмотрим, как создать документ, например, "Движение продукции и материалов" с видом операции "Передача материалов в кладовую" в конфигурации 1С:ERP 2.5, используя более надежный механизм заполнения через структуру и метод Заполнить().

Наш первоначальный подход к созданию документа и его ограничения

Изначально мы могли бы создавать документ, используя прямой доступ к его реквизитам, как показано в следующем примере:


Если ЗначениеЗаполнено(Объект.Получатель) Тогда
    НовыйДок = Документы.ДвижениеПродукцииИМатериалов.СоздатьДокумент;
    НовыйДок.Статус = Перечисления.СтатусыДвиженияПродукцииИМатериалов.Принято;
    НовыйДок.Дата = ТекущаяДата;
    НовыйДок.ХозяйственнаяОперация = Перечисления.ХозяйственныеОперации.ПередачаМатериаловВКладовую;
    НовыйДок.Ответственный = Пользователи.ТекущийПользователь;
    НовыйДок.Отправитель = Объект.Отправитель;
    НовыйДок.Получатель = Объект.Получатель;
    НовыйДок.НалогообложениеНДС = Перечисления.ТипыНалогообложенияНДС.ПродажаОблагаетсяНДС;
    НовыйДок.Валюта = Справочники.Валюты.НайтиПоКоду("643");
    НовыйДок.ВариантПриемкиТоваров = Перечисления.ВариантыПриемкиТоваров.РазделенаТолькоПоНакладным;
    НовыйДок.ВидЦены = Объект.Отправитель.УчетныйВидЦены;

    Для каждого ТекущаяСтрока Из Объект.Список Цикл
        Если ЗначениеЗаполнено(ТекущаяСтрока.Переместить) Тогда
            СтрТаб = НовыйДок.Товары.Добавить;
            ЗаполнитьЗначенияСвойств(СтрТаб,ТекущаяСтрока);
            СтрТаб.Количество = ТекущаяСтрока.Переместить;
            СтрТаб.КоличествоУпаковок = ТекущаяСтрока.Переместить;
        КонецЕсли;
    КонецЦикла;

    НовыйДок.Записать;
    СсылкаНаДок = НовыйДок.Ссылка;
    Возврат СсылкаНаДок;
Иначе
    Сообщить("Заполните склад получатель");
КонецЕсли;

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

Использование метода Заполнить() со структурой для надежного создания документа

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

Шаг 1: Создание объекта документа и структуры данных

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


Если ЗначениеЗаполнено(Объект.Получатель) Тогда
    // Создаем новый объект документа
    НовыйДок = Документы.ДвижениеПродукцииИМатериалов.СоздатьДокумент;

    // Создаем структуру для передачи данных
    ДанныеЗаполнения = Новый Структура;

    // Заполняем структуру ключевыми реквизитами
    ДанныеЗаполнения.Вставить("Статус", Перечисления.СтатусыДвиженияПродукцииИМатериалов.Принято);
    ДанныеЗаполнения.Вставить("Дата", ТекущаяДата());
    ДанныеЗаполнения.Вставить("ХозяйственнаяОперация", Перечисления.ХозяйственныеОперации.ПередачаМатериаловВКладовую);
    ДанныеЗаполнения.Вставить("Ответственный", Пользователи.ТекущийПользователь());
    ДанныеЗаполнения.Вставить("Отправитель", Объект.Отправитель);
    ДанныеЗаполнения.Вставить("Получатель", Объект.Получатель);
    ДанныеЗаполнения.Вставить("НалогообложениеНДС", Перечисления.ТипыНалогообложенияНДС.ПродажаОблагаетсяНДС);
    ДанныеЗаполнения.Вставить("Валюта", Справочники.Валюты.НайтиПоКоду("643"));
    ДанныеЗаполнения.Вставить("ВариантПриемкиТоваров", Перечисления.ВариантыПриемкиТоваров.РазделенаТолькоПоНакладным);
    ДанныеЗаполнения.Вставить("ВидЦены", Объект.Отправитель.УчетныйВидЦены);

    // ... другие реквизиты по необходимости

    // Вызываем метод Заполнить()
    НовыйДок.Заполнить(ДанныеЗаполнения);

    // Далее мы можем дозаполнить те реквизиты, которые не заполнились автоматически,
    // или табличные части, которые не могут быть заполнены методом Заполнить() напрямую.

    // Заполнение табличной части "Товары" (это обычно делается вручную после Заполнить)
    Для каждого ТекущаяСтрока Из Объект.Список Цикл
        Если ЗначениеЗаполнено(ТекущаяСтрока.Переместить) Тогда
            СтрТаб = НовыйДок.Товары.Добавить;
            ЗаполнитьЗначенияСвойств(СтрТаб,ТекущаяСтрока); // Можно использовать для копирования общих свойств
            СтрТаб.Количество = ТекущаяСтрока.Переместить;
            СтрТаб.КоличествоУпаковок = ТекущаяСтрока.Переместить;
        КонецЕсли;
    КонецЦикла;

    НовыйДок.Записать();
    СсылкаНаДок = НовыйДок.Ссылка;
    Возврат СсылкаНаДок;
Иначе
    Сообщить("Заполните склад получатель");
КонецЕсли;

Шаг 2: Вся магия в методе Заполнить()

Ключевая особенность этого подхода кроется в строке НовыйДок.Заполнить(ДанныеЗаполнения). Метод Заполнить() выполняет следующее:

  1. Он автоматически пытается заполнить реквизиты документа, чьи имена совпадают с ключами в переданной структуре ДанныеЗаполнения.
  2. При этом он не вызовет ошибку, если в структуре есть данные, для которых нет соответствующих реквизитов в документе. Лишние данные просто будут проигнорированы.
  3. Метод вызывает процедуру ОбработкаЗаполнения в модуле объекта документа, которая содержит основную логику начального заполнения, определенную разработчиками конфигурации.
  4. Он учитывает свойство реквизита "Заполнять из данных заполнения" в метаданных, о котором мы поговорим далее.

Таким образом, мы передаем только те данные, которые считаем основными, а остальное дозаполняется по логике конфигурации, что существенно снижает вероятность ошибок из-за пропущенных или некорректно заполненных служебных реквизитов.

Роль свойства "Заполнять из данных заполнения"

Для того чтобы реквизиты документа могли автоматически заполняться из переданной структуры через метод Заполнить(), они должны иметь установленное свойство "Заполнять из данных заполнения" в метаданных конфигурации. Это свойство находится в палитре свойств реквизита. Рассмотрим его значение:

  1. Если это свойство установлено для реквизита, платформа будет пытаться заполнить его значением из структуры ДанныеЗаполнения, если ключ структуры совпадает с именем реквизита.
  2. Это особенно актуально для реквизитов составного типа. Если реквизит имеет составной тип, но свойство "Заполнять из данных заполнения" не установлено, он может не заполниться автоматически из структуры, даже если данные для него присутствуют. В таком случае нам придется присваивать значение этому реквизиту напрямую после вызова НовыйДок.Заполнить().
  3. Установка этого свойства обеспечивает корректную работу при создании документов на основании отборов в списках или при программном заполнении.

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

Процедура ОбработкаЗаполнения модуля объекта документа

Сердцем автоматического заполнения документа является процедура ОбработкаЗаполнения, расположенная в модуле объекта документа. Эта процедура вызывается платформой в нескольких случаях:

Принцип работы ОбработкаЗаполнения

Когда мы вызываем НовыйДок.Заполнить(ДанныеЗаполнения), происходит следующая последовательность:

  1. Платформенное заполнение: Сначала платформа пытается автоматически заполнить реквизиты документа из переданной структуры ДанныеЗаполнения, учитывая свойство "Заполнять из данных заполнения".
  2. Вызов процедуры ОбработкаЗаполнения: Затем вызывается процедура ОбработкаЗаполнения(ДанныеЗаполнения, СтандартнаяОбработка) в модуле объекта документа. Здесь разработчики конфигурации прописывают дополнительную логику заполнения.

Пример структуры процедуры ОбработкаЗаполнения:


Процедура ОбработкаЗаполнения(ДанныеЗаполнения, СтандартнаяОбработка)

    // Сначала выполняются платформенные механизмы заполнения (см. выше)

    // Далее идет логика, разработанная в конфигурации.
    // Например, заполнение по данным из другого документа:
    Если ТипЗнч(ДанныеЗаполнения) = Тип("ДокументСсылка.ПлатежноеПоручениеИсходящее") Тогда
        // Заполнение реквизитов на основании платежного поручения
        ЭтотОбъект.Контрагент = ДанныеЗаполнения.Получатель;
        // ...
    ИначеЕсли ТипЗнч(ДанныеЗаполнения) = Тип("Структура") Тогда
        // Дополнительная логика заполнения, если передана структура
        // Здесь можно проверить наличие определенных ключей в структуре
        // и выполнить действия, которые не были сделаны платформой.

        Если ДанныеЗаполнения.Свойство("Организация") Тогда
            ЭтотОбъект.Организация = ДанныеЗаполнения.Организация;
        КонецЕсли;

        // Также здесь могут вызываться служебные процедуры для комплексного заполнения
        // Например, процедура, которая устанавливает статус или вид цены по умолчанию
        // на основе других реквизитов.
        ЗаполнитьДанныеПоОрганизации(ЭтотОбъект.Организация); // Пример вызова внутренней функции
    КонецЕсли;

    // В конце процедуры может быть выполнено общее заполнение для реквизитов,
    // не заполненных на предыдущих шагах, используя значения по умолчанию.
    Если Не ЗначениеЗаполнено(ЭтотОбъект.Валюта) Тогда
        ЭтотОбъект.Валюта = Константы.ВалютаРегламентированногоУчета.Получить();
    КонецЕсли;

    // Параметр СтандартнаяОбработка можно установить в Ложь,
    // если мы полностью берем на себя управление заполнением
    // СтандартнаяОбработка = Ложь;

КонецПроцедуры

Важно: При программном создании документа мы полагаемся на то, что процедура ОбработкаЗаполнения уже содержит всю необходимую логику. Однако, проконтролировать ее исполнение все равно нужно. В редких случаях некоторые реквизиты могут быть очищены или изменены обработчиком заполнения, даже если они были в структуре ДанныеЗаполнения.

Нюансы и рекомендации при программном заполнении

  1. Цепочки обработчиков событий: Метод Заполнить() и процедура ОбработкаЗаполнения не гарантируют автоматическое срабатывание всех обработчиков событий типа ПриИзменении для реквизитов формы или объекта. Если изменение одного реквизита должно повлечь за собой комплексное изменение других по сложной логике (например, при выборе контрагента заполняются договор, банк, менеджер), возможно, потребуется явно вызвать эти обработчики после Заполнить() для ключевых реквизитов. Обычно это делается через вызов соответствующих функций или процедур общего назначения, которые используются в этих обработчиках.
  2. Анализ типовых решений: Всегда анализируйте, как заполняются аналогичные документы в типовых конфигурациях 1С (особенно в ERP 2.5). Откройте модуль объекта документа, найдите процедуру ОбработкаЗаполнения, изучите, какие реквизиты там заполняются, какие служебные функции вызываются. Это даст понимание ожидаемой логики и поможет избежать ошибок.
  3. Минимальное заполнение: Мы рекомендуем изначально передавать в структуру ДанныеЗаполнения минимально необходимый набор реквизитов. После вызова Заполнить(), проверьте, какие реквизиты остались незаполненными, и только потом дописывайте их явное присвоение или добавляйте в структуру. Такой подход позволяет полагаться на стандартную логику конфигурации и уменьшает объем поддерживаемого кода.
  4. Табличные части: Табличные части, как правило, не заполняются методом Заполнить() автоматически из структуры. Их приходится заполнять итеративно, добавляя новые строки и присваивая значения их реквизитам. Однако, можно использовать вспомогательные функции или методы для автоматизации этого процесса, которые могут принимать параметры из структуры.
  5. Контекст выполнения: Помните, что программная работа с объектами документов (создание, заполнение, запись, проведение) всегда должна выполняться в серверном контексте, даже если вы инициируете ее из управляемой формы на клиенте.
  6. Заполнение по умолчанию: Помимо логики в ОбработкаЗаполнения, в 1С существуют пользовательские настройки по умолчанию (например, основной склад, организация, подразделение), которые также могут автоматически заполняться при создании нового документа. Учитывайте их влияние.

Пример полного кода с использованием метода Заполнить()

Давайте скомбинируем все полученные знания в единый, более надежный код для создания документа "Движение продукции и материалов":


// В серверном модуле или процедуре, вызываемой на сервере
Функция СоздатьДокументДвижениеПродукцииИМатериалов(ОбъектОбработки) Экспорт

    Если Не ЗначениеЗаполнено(ОбъектОбработки.Получатель) Тогда
        Сообщить("Необходимо заполнить склад получатель для создания документа!");
        Возврат Неопределено;
    КонецЕсли;

    // 1. Создаем новый объект документа
    НовыйДок = Документы.ДвижениеПродукцииИМатериалов.СоздатьДокумент();

    // 2. Формируем структуру с ключевыми данными для заполнения
    ДанныеЗаполнения = Новый Структура;
    ДанныеЗаполнения.Вставить("Статус", Перечисления.СтатусыДвиженияПродукцииИМатериалов.Принято);
    ДанныеЗаполнения.Вставить("Дата", ТекущаяДата());
    ДанныеЗаполнения.Вставить("ХозяйственнаяОперация", Перечисления.ХозяйственныеОперации.ПередачаМатериаловВКладовую);
    ДанныеЗаполнения.Вставить("Ответственный", Пользователи.ТекущийПользователь());
    ДанныеЗаполнения.Вставить("Отправитель", ОбъектОбработки.Отправитель);
    ДанныеЗаполнения.Вставить("Получатель", ОбъектОбработки.Получатель);
    ДанныеЗаполнения.Вставить("НалогообложениеНДС", Перечисления.ТипыНалогообложенияНДС.ПродажаОблагаетсяНДС);
    ДанныеЗаполнения.Вставить("Валюта", Справочники.Валюты.НайтиПоКоду("643"));
    ДанныеЗаполнения.Вставить("ВариантПриемкиТоваров", Перечисления.ВариантыПриемкиТоваров.РазделенаТолькоПоНакладным);
    
    // Если ВидЦены не заполняется автоматически через Заполнить()
    // и свойство "Заполнять из данных заполнения" не установлено,
    // его можно добавить в структуру или присвоить напрямую позже.
    ДанныеЗаполнения.Вставить("ВидЦены", ОбъектОбработки.Отправитель.УчетныйВидЦены);

    // 3. Вызываем метод Заполнить()
    НовыйДок.Заполнить(ДанныеЗаполнения);

    // 4. Дозаполнение реквизитов, которые не были заполнены автоматически
    // Например, если какой-то реквизит составного типа не заполнился из структуры
    // из-за отсутствия свойства "Заполнять из данных заполнения".
    // В данном случае, мы уже передали все в структуру.

    // 5. Заполнение табличной части "Товары"
    Для каждого ТекущаяСтрока Из ОбъектОбработки.Список Цикл
        Если ЗначениеЗаполнено(ТекущаяСтрока.Переместить) Тогда
            СтрТаб = НовыйДок.Товары.Добавить();
            // Копируем основные свойства из строки источника
            ЗаполнитьЗначенияСвойств(СтрТаб, ТекущаяСтрока,
                "Номенклатура, Характеристика, Серия, ЕдиницаИзмерения, Упаковка"); // Указываем конкретные свойства для копирования

            СтрТаб.Количество = ТекущаяСтрока.Переместить;
            СтрТаб.КоличествоУпаковок = ТекущаяСтрока.Переместить;
            // Возможно, потребуется заполнить другие реквизиты строки ТЧ
            // СтрТаб.Цена = ...
            // СтрТаб.Сумма = ...
        КонецЕсли;
    КонецЦикла;

    // Проверяем, что табличная часть не пустая перед записью
    Если НовыйДок.Товары.Количество() = 0 Тогда
        Сообщить("Табличная часть 'Товары' пуста. Документ не будет создан.");
        Возврат Неопределено;
    КонецЕсли;

    // 6. Записываем документ
    Попытка
        НовыйДок.Записать(РежимЗаписиДокумента.Проведение); // Можно попробовать сразу провести
        Сообщить("Документ 'Движение продукции и материалов' успешно создан и проведен. Ссылка: " + НовыйДок.Ссылка);
        Возврат НовыйДок.Ссылка;
    Исключение
        Сообщить("Ошибка при записи/проведении документа: " + ОписаниеОшибки());
        Возврат Неопределено;
    КонецПопытки;

КонецФункции

Используя данный подход, мы значительно повышаем надежность программного создания документов, делегируя часть работы по заполнению платформе 1С и логике, заложенной в конфигурацию разработчиками. Это особенно ценно в больших и сложных конфигурациях, таких как 1С:ERP 2.5, где количество реквизитов и взаимосвязанной логики может быть очень велико. Для понимания логики последующей обработки и записи созданных объектов пригодится схема типового механизма проведения документов в ERP 2.5.

← На главную