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