Почему реквизит документа записывается только со второго раза и как это исправить

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

В практике разработки на платформе 1С:Предприятие 8 часто встречается ситуация, когда программное изменение реквизитов документа в процедуре ПередЗаписью ведет себя странно: на форме документа изменения видны сразу, а в списке документов или при проверке программным кодом данные обновляются только после повторного нажатия кнопки «Записать». В этой статье мы подробно разберем, почему так происходит, изучим механизмы работы транзакций и рассмотрим несколько способов решения этой задачи.

Проанализируем ситуацию: в чем корень проблемы

Рассмотрим типичный сценарий. У нас есть документ (например, «Реализация товаров и услуг»), в котором имеется реквизит ВестиВетУчет (Булево). Логика проста: если в табличной части «Товары» есть хотя бы одна номенклатура, у которой в карточке стоит флажок ам_ВестиВетеринарныйУчет, то и в самом документе флаг должен установиться автоматически.

Разработчик пишет следующий код в модуле объекта:


Процедура ПередЗаписью(Отказ, РежимЗаписи, РежимПроведения)
    Запрос = Новый Запрос;
    Запрос.Текст = 
    "ВЫБРАТЬ
    |    МАКСИМУМ(РеализацияТоваровУслугТовары.Номенклатура.ам_ВестиВетеринарныйУчет) КАК ВестиВетУчет
    |ИЗ
    |    Документ.РеализацияТоваровУслуг.Товары КАК РеализацияТоваровУслугТовары
    |ГДЕ
    |    РеализацияТоваровУслугТовары.Ссылка = &Ссылка";
    
    Запрос.УстановитьПараметр("Ссылка", Ссылка);
    // ... выполнение запроса и присвоение значения реквизиту
КонецПроцедуры

Выясним причину ошибки: в момент выполнения события ПередЗаписью данные в базе данных (в таблицах SQL) еще не обновлены. Когда мы делаем запрос к таблице Документ.РеализацияТоваровУслуг.Товары, СУБД возвращает нам те данные, которые были сохранены в прошлый раз. Если это новый документ — запрос не вернет ничего. Если мы добавили новую строку в существующий документ — запрос «увидит» только старый состав строк.

Именно поэтому возникает эффект «второго раза»: при первой записи запрос берет старые данные, а при второй записи он уже видит данные, которые были записаны при первом нажатии.

Решение №1: Использование данных объекта вместо запроса к таблице документа

Чтобы получить актуальные данные, которые пользователь только что ввел на форме (или которые были изменены программно в объекте), нам не нужно обращаться к базе данных через запрос к таблице документа. Эти данные уже находятся у нас в оперативной памяти — в коллекции ЭтотОбъект.Товары.

Рассмотрим правильный вариант запроса. Мы можем получить массив всей номенклатуры из табличной части и обратиться запросом не к документу, а к справочнику Номенклатура.


Процедура ПередЗаписью(Отказ, РежимЗаписи, РежимПроведения)
    
    // Получаем массив уникальных ссылок номенклатуры из объекта в памяти
    МассивНоменклатуры = ЭтотОбъект.Товары.ВыгрузитьКолонку("Номенклатура");
    
    Запрос = Новый Запрос;
    Запрос.Текст = 
    "ВЫБРАТЬ
    |    ИСТИНА КАК ЕстьВетеринарныйТовар
    |ИЗ
    |    Справочник.Номенклатура КАК Номенклатура
    |ГДЕ
    |    Номенклатура.Ссылка В(&СписокНоменклатуры)
    |    И Номенклатура.ам_ВестиВетеринарныйУчет = ИСТИНА";
    
    Запрос.УстановитьПараметр("СписокНоменклатуры", МассивНоменклатуры);
    
    РезультатЗапроса = Запрос.Выполнить();
    
    // Если запрос не пустой, значит нашли хотя бы одну позицию с вет. учетом
    ВестиВетУчет = НЕ РезультатЗапроса.Пустой();
    
КонецПроцедуры

В данном случае мы гарантированно работаем с актуальным составом табличной части, так как передаем список ссылок непосредственно из объекта, который готовится к записи.

Решение №2: Обход коллекции в цикле (без запроса)

Многие разработчики задаются вопросом: что быстрее — запрос к справочнику или простой перебор строк? Если в вашем документе обычно немного строк (до нескольких сотен), перебор коллекции может быть даже эффективнее, так как не требует обращения к СУБД для построения дерева запроса и передачи параметров.

Рассмотрим пример реализации через цикл:


ВестиВетУчет = Ложь;
Для Каждого СтрокаТЧ Из Товары Цикл
    // Проверяем реквизит через точку. 
    // Внимание: это вызывает обращение к БД, если номенклатура не в кэше.
    Если СтрокаТЧ.Номенклатура.ам_ВестиВетеринарныйУчет Тогда
        ВестиВетУчет = Истина;
        Прервать; // Достаточно найти одну строку
    КонецЕсли;
КонецЦикла;

Важный нюанс: обращение через точку СтрокаТЧ.Номенклатура.ам_ВестиВетеринарныйУчет является "неявным" запросом. Если номенклатуры в документе много и она разная, это может создать нагрузку (N+1 запрос). В таких случаях вариант с Запросом (Решение №1) является более профессиональным и производительным.

Почему изменения не сразу видны в форме списка?

Даже если мы исправили код в ПередЗаписью, иногда пользователи жалуются, что в динамическом списке флажок обновляется "с задержкой". Это связано с кэшированием данных на стороне клиента.

Для гарантированного обновления формы списка после записи объекта рекомендуется в модуле формы документа в процедуре ПослеЗаписи использовать метод оповещения:


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

Подведем итоги

Для того чтобы данные записывались с первого раза, запомним основные правила:

  1. Никогда не обращайтесь в ПередЗаписью к таблицам самого записываемого документа через запрос. В этот момент в базе данных лежат еще старые (неактуальные) значения.
  2. Используйте данные самого объекта. Читайте табличные части напрямую из коллекций Товары, Услуги и т.д.
  3. Если нужно проверить свойства ссылок (номенклатуры, контрагента), передавайте их массив в запрос к соответствующему справочнику.
  4. Учитывайте пустые значения. Перед передачей массива в запрос желательно очистить его от пустых ссылок с помощью методов работы с массивами, чтобы запрос не выполнял лишнюю работу.

Соблюдение этих архитектурных принципов 1С позволит избежать досадных ошибок с "повторной записью" и обеспечит высокую скорость работы вашей конфигурации.

← На главную