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