Как правильно программно создать элемент справочника и подставить его в табличную часть документа при записи?

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

В процессе разработки на платформе 1С:Предприятие 8.3 часто возникает задача автоматизации заполнения данных. Типичный пример: при записи документа поступления товаров необходимо для каждой строки создать новую запись в справочнике Партии и тут же подставить ссылку на эту новую партию в табличную часть документа. Начинающие разработчики часто сталкиваются здесь с трудностями, связанными с жизненным циклом записи объекта и обновлением данных на форме. Разберем эту ситуацию подробно.

Анализируем проблему: ошибка «Несоответствие версии»

В исходном примере автор пытался реализовать логику в процедуре ПриЗаписиНаСервере, используя метод РеквизитФормыВЗначение("Объект") и последующий вызов Об.Записать(). Это привело к ошибке: «Операция не может быть выполнена из-за несоответствия версии или отсутствия записи базы данных». Выясним причину этой ошибки.

Когда мы находимся внутри стандартного процесса записи (например, в событии ПриЗаписи), платформа уже открыла транзакцию и подготовила объект к сохранению. Вызывая РеквизитФормыВЗначение, мы создаем в оперативной памяти копию нашего объекта. Когда мы вызываем Об.Записать() для этой копии, данные в базе обновляются, и версия объекта инкрементируется (увеличивается). Однако основная процедура записи, инициированная пользователем, «не знает» об этом изменении. Когда она пытается завершить свою транзакцию, она видит, что версия объекта в базе данных уже отличается от той, которую она начала записывать. Платформа блокирует запись, чтобы предотвратить потерю данных (так работает механизм оптимистической блокировки).

Решение №1: Использование события ПередЗаписьюНаСервере в модуле формы

Для того чтобы избежать конфликтов версий и лишних вызовов записи, рассмотрим правильный алгоритм работы в модуле управляемой формы. Нам необходимо использовать обработчик события ПередЗаписьюНаСервере. В этом обработчике доступен параметр ТекущийОбъект, который представляет собой сам документ в виде объекта базы данных еще до того, как он был окончательно сохранен.

Проанализируем шаги реализации:

  1. Обходим строки табличной части именно в ТекущийОбъект.
  2. Проверяем, не заполнена ли уже партия (чтобы не создавать дубли при повторной записи документа).
  3. Создаем новый элемент справочника, заполняем его реквизиты и записываем.
  4. Присваиваем полученную ссылку в колонку табличной части ТекущийОбъект.

Посмотрим на пример кода:


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

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

Решение №2: Перенос логики в Модуль объекта (рекомендуемый подход)

Профессиональная разработка в 1С подразумевает, что бизнес-логика должна работать независимо от того, записывается документ через форму или программно (например, при загрузке данных из внешнего файла). Если мы оставим код в форме, то при программном создании документа партии создаваться не будут. Поэтому разберем, как реализовать это в Модуле объекта документа.

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

Рассмотрим структуру кода в модуле объекта:


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

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

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

Важные нюансы и оптимизация

Разберем несколько критически важных моментов, которые помогут сделать ваш код более надежным и производительным:

  1. Проверка на дубли: Всегда проверяйте, существует ли уже такая партия в базе. Если пользователь несколько раз нажмет кнопку «Записать», ваш код может создать десятки одинаковых записей в справочнике. Используйте поиск по владельцу и ключевым реквизитам (цена, серия, дата).
  2. Использование транзакции: Поскольку ПередЗаписью выполняется внутри транзакции документа, если при записи справочника произойдет ошибка, вся транзакция (включая запись документа) будет отменена. Это гарантирует целостность данных: у вас не будет документа с пустой партией или партии без документа.
  3. Владелец справочника: Если справочник Партии подчинен справочнику Номенклатура, обязательно заполняйте реквизит Владелец. Это не только логично с точки зрения структуры данных, но и ускоряет последующий поиск партий в базе.
  4. Реквизит ОбменДанными.Загрузка: Всегда добавляйте проверку Если ОбменДанными.Загрузка Тогда Возврат; КонецЕсли; в начало обработчиков модуля объекта. Это предотвратит выполнение лишней логики при автоматическом обмене данными между базами.

Проанализировав ситуацию, мы видим, что главная ошибка заключалась в попытке «записать записанное». Помните: в серверных обработчиках записи документа (и в форме, и в модуле объекта) платформа сама берет на себя ответственность за фиксацию изменений в БД. Ваша задача — лишь подготовить данные в ТекущийОбъект или ЭтотОбъект.

Таким образом, использование ПередЗаписьюНаСервере в форме или ПередЗаписью в модуле объекта — это наиболее эффективный и корректный способ программного манипулирования данными табличных частей при сохранении документа.

← На главную