При работе с платформой 1С:Предприятие разработчики часто сталкиваются с задачей программного создания документов, которые должны автоматически заполняться по определенным правилам, аналогично тому, как это происходит при интерактивном вводе документа пользователем. Однако, на практике обнаруживается, что простое создание объекта документа методом СоздатьДокумент() не приводит к автоматическому заполнению всех необходимых реквизитов, таких как ставки НДС, контрагенты, суммы и другие детали. Эта ситуация вызывает много вопросов и требует глубокого понимания внутренних механизмов платформы. Давайте разберем, почему возникает такая разница между интерактивным и программным созданием, и какие методы позволяют добиться желаемого результата.
Когда пользователь создает новый документ интерактивно, например, через команду "Создать" в списке документов, платформа выполняет целый ряд действий. Открывается форма документа, и в процессе ее инициализации срабатывают различные обработчики, как на клиенте, так и на сервере. Эти обработчики могут:
ОбработкаЗаполнения объекта документа. Если документ создается на основании другого документа или с использованием определенных данных заполнения, этот обработчик играет ключевую роль в первоначальном заполнении реквизитов.&НаКлиенте), так и серверными (&НаСервере, &НаСервереБезКонтекста), но они тесно связаны с контекстом открытой формы.ЗначениеЗаполнения или ЗаполнятьИзДанныхЗаполнения, которые используются платформой при создании нового документа.В то же время, при программном создании документа методом Документы.ИмяДокумента.СоздатьДокумент(), мы получаем в памяти объект, который, по сути, является "сырой" заготовкой. Этот объект не имеет связанной формы, и следовательно, никакие обработчики модуля формы или алгоритмы, завязанные на пользовательский интерфейс, не выполняются автоматически. Именно поэтому многие реквизиты остаются пустыми или заполняются значениями по умолчанию, а не теми, которые ожидает увидеть пользователь при интерактивном вводе.
Ключевая проблема: не существует универсального метода Объект.ВыполнитьВсеОбработчикиФормыПриСозданииНовогоДокумента(). Логика заполнения документов часто распределена между модулем объекта, модулем формы и подписками на события, что затрудняет ее централизованное воспроизведение. Более того, обратиться к процедурам модуля формы без явного создания и открытия формы обычно невозможно, а часть логики может быть реализована на клиенте и требовать прямого взаимодействия с элементами интерфейса.
Несмотря на вышеупомянутые сложности, существуют эффективные подходы для программного заполнения документов в 1С. Рассмотрим их подробнее.
ОбработкаЗаполненияСобытие ОбработкаЗаполнения является одним из наиболее важных механизмов для автоматического заполнения реквизитов документа. Оно вызывается платформой в нескольких случаях:
Заполнить() объекта документа.Если в конфигурации логика первоначального заполнения документа реализована именно в обработчике ОбработкаЗаполнения модуля объекта, то для ее выполнения при программном создании достаточно вызвать метод Заполнить(). В обработчике этого события должен быть описан алгоритм заполнения реквизитов документа на основании переданных данных. Если данные заполнения представляют собой структуру, это означает, что объект создается программно с передачей этой структуры через методы ОткрытьФорму(), ПолучитьФорму() или Заполнить(). Рекомендуется использовать исключение (ВызватьИсключение) для оповещения пользователя о причинах отказа в заполнении, а не пытаться отменить действие через параметр Отказ (которого нет) или СтандартнаяОбработка = Ложь (что не отменяет действие).
Рассмотрим пример:
// Создаем новый объект документа
ОбъектДокумента = Документы.ПлатежноеПоручениеВходящее.СоздатьДокумент();
// Устанавливаем базовые данные, которые могут быть использованы в ОбработкаЗаполнения
// Например, если документ создается на основании, или если передаются параметры
СтруктураДанныхЗаполнения = Новый Структура();
СтруктураДанныхЗаполнения.Вставить("Контрагент", СсылкаНаКонтрагента);
СтруктураДанныхЗаполнения.Вставить("Сумма", 12500);
СтруктураДанныхЗаполнения.Вставить("Организация", СсылкаНаОрганизацию);
СтруктураДанныхЗаполнения.Вставить("Валюта", СсылкаНаВалюту);
// Вызываем метод Заполнить(), который инициирует событие ОбработкаЗаполнения
// и передаем туда структуру с данными
ОбъектДокумента.Заполнить(СтруктураДанныхЗаполнения);
// После выполнения ОбработкаЗаполнения, реквизиты объекта будут заполнены
// согласно логике, описанной в этом обработчике.
// Теперь мы можем дозаполнить специфичные реквизиты, если это необходимо
// (например, табличные части или реквизиты, которые не были учтены в ОбработкаЗаполнения).
// Пример дозаполнения табличной части, если СтавкаНДС не заполнилась автоматически
// и требует ручного добавления в расшифровку платежа.
Если ОбъектДокумента.РасшифровкаПлатежа.Количество() = 0 Тогда
НоваяСтрока = ОбъектДокумента.РасшифровкаПлатежа.Добавить();
НоваяСтрока.СтавкаНДС = Перечисления.СтавкиНДС.НДС20; // Или НДС10, БезНДС
НоваяСтрока.СуммаНДС = 2083.33; // Расчетная сумма НДС (12500 / 1.2 * 0.2)
НоваяСтрока.Сумма = 12500; // Сумма по строке с учетом НДС
НоваяСтрока.Содержание = "Оплата за товары";
НоваяСтрока.СтатьяДвиженияДенежныхСредств = СсылкаНаСтатьюДДС;
КонецЕсли;
// Записываем документ. Если документ должен быть проведен, можно использовать Провести()
// или Записать(РежимЗаписиДокумента.Проведение).
ОбъектДокумента.Записать(РежимЗаписиДокумента.Запись);
Важно помнить, что если логика заполнения в ОбработкаЗаполнения ожидает определенные "данные заполнения", их нужно передать в метод Заполнить() в виде структуры или ссылки на объект-основание.
Часто одного лишь вызова Заполнить() бывает недостаточно, особенно в типовых конфигурациях, где часть логики заполнения может быть распределена или находиться в модулях форм. В таких случаях приходится программно, строка за строкой, заполнять все необходимые реквизиты документа. Этот подход требует глубокого анализа интерактивного поведения документа.
Как выяснить, что заполняется?
ПриСозданииНаСервере, ОбработкаЗаполнения, ПриЗаписи, ПередЗаписью) и, что особенно важно, в модуле формы (ПриСозданииНаСервере, ПриОткрытии, ПриИзменении полей). Это позволит вам понять, какие реквизиты и в какой момент заполняются, а также какие процедуры выполняются.После того как вы выяснили последовательность и логику заполнения, вы должны воспроизвести ее в своем программном коде. Это означает, что нужно будет вручную присвоить значения всем необходимым реквизитам "шапки" документа и добавить строки в табличные части, заполняя их реквизиты. Для табличных частей используется метод Добавить() для создания новой строки и последующее заполнение ее реквизитов.
Пример заполнения табличной части, как в случае с НДС, упомянутом в обсуждении:
// Предположим, ОбъектДокумента уже создан методом СоздатьДокумент()
// и основные реквизиты шапки (Контрагент, Организация) уже заполнены.
// Добавляем строки в табличную часть "РасшифровкаПлатежа"
// Каждая строка здесь может представлять отдельную услугу/товар с своей ставкой НДС.
НоваяСтрока = ОбъектДокумента.РасшифровкаПлатежа.Добавить();
НоваяСтрока.НазначениеПлатежа = "Оплата за ноутбук Dell XPS";
НоваяСтрока.Сумма = 83333.33; // Сумма без НДС
НоваяСтрока.СтавкаНДС = Перечисления.СтавкиНДС.НДС20;
НоваяСтрока.СуммаНДС = 16666.67;
НоваяСтрока.СтатьяДвиженияДенежныхСредств = СсылкаНаСтатьюДДС;
// Если нужна еще одна строка
НоваяСтрока2 = ОбъектДокумента.РасшифровкаПлатежа.Добавить();
НоваяСтрока2.НазначениеПлатежа = "Доставка";
НоваяСтрока2.Сумма = 500;
НоваяСтрока2.СтавкаНДС = Перечисления.СтавкиНДС.НДС20;
НоваяСтрока2.СуммаНДС = 100;
НоваяСтрока2.СтатьяДвиженияДенежныхСредств = СсылкаНаСтатьюДДС;
// После заполнения всех реквизитов и табличных частей
// Выполняем запись документа
ОбъектДокумента.Записать(РежимЗаписиДокумента.Запись);
Важный момент: если какой-либо реквизит, необходимый для проведения документа, не отображается на форме (потому что пользователь его не должен заполнять), это не означает, что его не нужно заполнять программно. Такие "скрытые" реквизиты также придется выявить и присвоить им значения. Следует помнить, что реквизиты формы, которые не являются реквизитами самого объекта документа или табличной части, не могут быть заполнены напрямую через объект документа. Они привязаны к реквизитам объекта через свойство "Данные" и заполняются автоматически при открытии формы.
Как уже отмечалось, значительная часть логики заполнения может находиться в модулях форм и быть недоступной при прямом создании объекта. В идеале, вся бизнес-логика должна быть реализована в модулях объектов или общих модулях, чтобы ее можно было вызывать независимо от интерфейса. Однако, если вы столкнулись с ситуацией, когда критически важный алгоритм живет только в модуле формы, и переносить его в модуль объекта невозможно или нецелесообразно (например, при работе с закрытой для изменений типовой конфигурацией), можно рассмотреть обходные пути.
Один из таких обходных путей, хотя и не рекомендованный для фонового создания документов из-за накладных расходов и потенциальных проблем с контекстом, заключается в программном открытии формы документа, вызове необходимых экспортных процедур/функций из ее модуля, и последующем получении результатов.
Пример такого подхода (предположим, есть "крутой алгоритм вычисления НДС", который доступен только через форму):
// Шаг 1: Создаем и записываем пустой документ, чтобы получить ссылку
// Это нужно, потому что форма открывается для существующего объекта.
Док = Документы.ПлатежноеПоручениеВходящее.СоздатьДокумент();
Док.Контрагент = СсылкаНаКонтрагента;
Док.Сумма = 10000;
Док.Организация = СсылкаНаОрганизацию;
Док.Валюта = СсылкаНаВалюту;
// Если у нового документа есть основание, то в обработке заполнения можно подсмотреть,
// как интерактивно создается новый документ.
// Также можно использовать свойства реквизитов документа ЗначениеЗаполнения, ЗаполнятьИзДанныхЗаполнения
// или обработчик события документа ОбработкаЗаполнения.
Док.Записать(); // Записываем, чтобы получить ссылку
// Шаг 2: Получаем форму документа по ссылке
ФормаДокумента = Док.Ссылка.ПолучитьФорму("ФормаДокумента");
// Если логика заполнения находится в модуле формы, иногда возникает необходимость
// вызвать серверные процедуры из клиентского контекста.
// Это можно сделать, описав серверные процедуры с директивами компиляции &НаСервере
// или &НаСервереБезКонтекста в модуле формы или в общем модуле.
// Шаг 3: Вызываем экспортную процедуру/функцию из модуля формы
// (Предполагается, что в модуле формы есть экспортная процедура,
// например, "РассчитатьНДСПоТабличнойЧастиНаСервере" или "ПолучитьСтавкуНДСПоУмолчанию")
Если ФормаДокумента.Модифицированность Тогда // Если что-то изменилось в форме при открытии
ФормаДокумента.Записать(); // Сохраняем изменения, если форма их внесла (в объект, который еще не записан)
КонецЕсли;
// Если нам нужно получить значение из формы:
// Например, если на форме есть функция ЭкспортнаяФункцияРасчетаНДС()
Попытка
// Вызов экспортной процедуры из формы для выполнения расчетов
// Например, если на форме есть процедура РассчитатьТабличнуюЧастьНаСервере()
ФормаДокумента.РассчитатьТабличнуюЧастьНаСервере();
// Получаем обновленный объект после работы формы
ОбъектДокумента = Док.ПолучитьОбъект();
// Теперь объект должен содержать реквизиты, заполненные логикой формы
// Можно провести дополнительные проверки или дозаполнения
Если ОбъектДокумента.РасшифровкаПлатежа.Количество() > 0 Тогда
Сообщить("Табличная часть заполнена логикой формы.");
// Пример: Получим ставку НДС из первой строки, если она заполнена
СтавкаНДСИзФормы = ОбъектДокумента.РасшифровкаПлатежа[0].СтавкаНДС;
КонецЕсли;
ОбъектДокумента.Записать(РежимЗаписиДокумента.Запись);
Исключение
Сообщить("Не удалось вызвать метод из формы: " + ОписаниеОшибки());
КонецПопытки;
// Шаг 4: Закрываем форму, если она была открыта только для вызова алгоритма
// Это важно для освобождения ресурсов.
ФормаДокумента.Закрыть();
// После программного создания и заполнения документа, можно открыть его форму для пользователя
// с помощью метода ОткрытьЗначение(СсылкаНаДокумент).
ВНИМАНИЕ: Этот подход следует использовать очень осторожно и только в крайних случаях, поскольку он:
Всегда предпочтительнее переносить бизнес-логику в модуль объекта или общие модули. Если требуется предварительное заполнение, можно использовать свойства реквизитов документа ЗначениеЗаполнения, ЗаполнятьИзДанныхЗаполнения или обработчик события документа ОбработкаЗаполнения.
На практике наиболее эффективным часто оказывается комбинированный подход:
ОбъектДокумента.Заполнить(ПараметрыЗаполнения), чтобы задействовать логику обработчика ОбработкаЗаполнения.Этот подход позволяет использовать существующую типовую логику там, где это возможно, и дополнять ее собственным кодом для тех случаев, где автоматическое заполнение отсутствует или недостаточно.
Помимо модулей объекта и форм, в типовых конфигурациях активно используются подписки на события и общие модули. Логика заполнения или корректировки реквизитов может быть скрыта именно там. Например, при записи или проведении документа, определенные реквизиты могут быть рассчитаны или изменены с помощью обработчиков подписок. При программном создании документа эти подписки, как правило, будут срабатывать, если вызывается метод Записать() или Провести(). Однако, если вам нужно выполнить эту логику до записи (например, для отображения корректных значений пользователю), вам придется вручную вызвать соответствующие экспортные процедуры из общих модулей, если они таковые имеются и доступны. Для вызова серверных процедур из клиентского кода, их следует описывать с директивами компиляции &НаСервере или &НаСервереБезКонтекста в модуле формы или в общем модуле, у которого установлено свойство "Сервер" и "Вызов сервера".
Всегда полезно провести поиск по имени реквизита (например, "СтавкаНДС") по всей конфигурации, чтобы определить все места, где этот реквизит заполняется или изменяется. Это даст полное представление о его жизненном цикле.
Программное заполнение документов в типовых конфигурациях (таких как 1С:Бухгалтерия, 1С:Управление торговлей, 1С:ERP) представляет собой особенно сложную задачу. Причины этому следующие:
В типовых конфигурациях могут быть предусмотрены механизмы автозаполнения по шаблонам (например, в 1С:Документооборот, 1С:ERP Управление холдингом), которые позволяют заполнять реквизиты документа из других документов или справочников. Также могут использоваться внешние обработки вида "Заполнение объекта" для загрузки данных из файлов или других источников, которые могут открывать форму документа и передавать ей контекст вызывающей формы для манипуляций с данными.
Поэтому при работе с типовыми конфигурациями крайне важно максимально использовать стандартные механизмы (ОбработкаЗаполнения, типовые экспортные функции) и минимизировать дублирование кода.
Для успешного программного создания и заполнения документов в 1С рекомендуем придерживаться следующих принципов:
ОбработкаЗаполнения: Если возможно, стройте свою логику таким образом, чтобы она максимально использовала или имитировала работу этого обработчика.1С:Тестировщик или встроенный в конфигуратор "Запуск для записи журнала действий пользователя". Эти инструменты помогут зафиксировать последовательность действий пользователя и понять, какие процедуры вызываются. Однако помните, что это инструменты для тестирования и анализа, а не для генерации программного кода для фонового создания документов.Программное создание документов с полным автоматическим заполнением — это задача, требующая внимательности и системного подхода. Прямого "дублирования" интерактивных действий одним методом нет, но при правильном понимании платформы и логики конфигурации, ее можно успешно решить.