При разработке обработок пакетного создания или загрузки документов разработчики часто сталкиваются с ситуацией, когда на форме документа отображается поле, отсутствующее в списке реквизитов самого объекта метаданных. Попытка обратиться к такому реквизиту через объектную модель приводит к ошибке, а попытка манипулировать формой вызывает массу сопутствующих проблем: необходимость интерактивного открытия окон, замедление работы системы и невозможность выполнения кода на стороне сервера или в фоновых заданиях.
Рассмотрим подробнее архитектуру хранения таких данных, выясним причину возникновения подобной проблемы на примере типовых конфигураций на обычных формах (например, УТП, УТ 10.3) и разберем правильный способ программного заполнения документа ЧекККМ.
Проанализируем устройство документа ЧекККМ. На форме документа кассир видит простое поле выбора вида оплаты (например, «Наличные» или «Платежная карта»). Однако на уровне структуры метаданных отдельного реквизита шапки с именем ВидОплаты у документа нет. Вместо этого в документе присутствует табличная часть Оплата, предназначенная для поддержки сложных, комбинированных оплат (когда часть чека оплачивается наличными, а часть картой или бонусами).
Для удобства пользователя разработчики типовых решений вывели на форму отдельный реквизит формы ВидОплаты, который визуально подменяет собой табличную часть в случае простой одиночной оплаты. Синхронизация между этим полем и реальными данными документа выполняется исключительно кодом модуля формы:
Оплата и помещает значение в реквизит формы.ПриИзмененииВидаОплаты(), которая программно создает или перезаписывает строку в табличной части Оплата:
Процедура ПриИзмененииВидаОплаты()
Если Оплата.Количество() = 0 Тогда
СтрокаОплаты = Оплата.Добавить();
Иначе
СтрокаОплаты = Оплата[0];
КонецЕсли;
СтрокаОплаты.ВидОплаты = ВидОплаты;
ПересчитатьАвтоматическиеСкидки();
КонецПроцедуры
Важно понимать: реквизиты формы — это временные данные пользовательского интерфейса, существующие только в оперативной памяти клиентского приложения на время жизни экранной формы. Они не сохраняются в базе данных при прямом вызове метода Записать() объекта.
Посмотрим на типичную ошибку: попытку получить форму документа, изменить ее реквизит и сохранить объект:
Ч = Документы.ЧекККМ.СоздатьДокумент();
// Заполнение реквизитов шапки...
фДокОбъ = Ч.ПолучитьФорму("ФормаДокумента");
фДокОбъ["ВидОплаты"] = Справочники.ВидыОплатЧекаККМ.НайтиПоРеквизиту("ТипОплаты", Перечисления.ТипыОплатЧекаККМ.Наличные);
фДокОбъ.Открыть();
Ч.Записать();
Такой подход влечет за собой критические ограничения:
Открыть(), форма не инициализируется корректно, а данные не попадут в объект. Если же открывать форму в цикле для десятков создаваемых документов, экран пользователя будет перегружен постоянно всплывающими окнами.ПолучитьФорму() и Открыть() принципиально недопустимы на сервере, в регламентных и фоновых заданиях, а также в веб-сервисах и HTTP-сервисах. Код с обращением к элементам формы завершится аварийной ошибкой.ПриИзменении, из-за чего табличная часть объекта все равно может остаться пустой.Разберем по шагам правильный алгоритм пакетного создания документа без обращения к элементам интерфейса. Работать необходимо напрямую с объектом метаданных и его табличными частями.
СоздатьДокумент() менеджера документов.ТипыОплатЧекаККМ).Оплата создаваемого объекта и заполняем ее реквизиты (вид оплаты и сумму).Записать() в нужном режиме (запись или проведение).Посмотрим на итоговый пример кода обработки:
// Создаем новый документ
ТвойЧек = Документы.ЧекККМ.СоздатьДокумент();
ТвойЧек.Дата = ТекущаяДата();
ТвойЧек.ВидОперации = Перечисления.ВидыОперацийЧекККМ.Продажа;
ТвойЧек.Организация = Справочники.Организации.НайтиПоКоду("2");
ТвойЧек.Склад = Справочники.Склады.НайтиПоКоду("000000001");
ТвойЧек.КассаККМ = Справочники.КассыККМ.НайтиПоКоду("23");
// Находим нужный вид оплаты по типу
ВидОплаты = Справочники.ВидыОплатЧекаККМ.НайтиПоРеквизиту("ТипОплаты", Перечисления.ТипыОплатЧекаККМ.Наличные);
// Заполняем табличную часть Оплата напрямую в объекте
СтрокаОплаты = ТвойЧек.Оплата.Добавить();
СтрокаОплаты.ВидОплаты = ВидОплаты;
СтрокаОплаты.Сумма = 1000; // Указываем итоговую сумму платежа
// Записываем документ в базу данных без открытия окон
Попытка
ТвойЧек.Записать(РежимЗаписиДокумента.Запись);
Сообщить("Успешно создан документ: " + Строка(ТвойЧек.Ссылка));
Исключение
Сообщить("Ошибка при записи документа: " + ОписаниеОшибки(), СтатусСообщения.Важное);
КонецПопытки;
Рассмотрим дополнительные нюансы, которые необходимо учитывать при массовом создании документов через код:
Записать().НачатьТранзакцию() и ЗафиксироватьТранзакцию(). Это существенно ускорит выполнение за счет пакетной фиксации изменений в базе данных.ТвойЧек.Заполнить(<ДанныеЗаполнения>), если в модуле объекта определен обработчик события ОбработкаЗаполнения.Таким образом, при работе с документами из внешних обработок всегда следует отделять физическую структуру хранения данных объекта от элементов управления его формы. Работа с табличной частью Оплата напрямую позволяет создавать документы в фоновом режиме, на сервере и без необходимости отображения диалоговых окон пользователю.