Почему не сохраняются изменения в документе 1С при программной перезаписи табличной части?

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

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

Анализ типичной ошибки: проблема вложенных циклов

Проанализируем ситуацию, с которой столкнулся автор темы. В его исходном коде использовался вложенный цикл: для каждой строки из таблицы значений на форме он получал объект документа и запускал еще один цикл по всем строкам табличной части (ТЧ) этого документа. В этом и кроется главная логическая ловушка.

Разберем, что происходит в таком случае: допустим, в документе 3 строки и в вашей ТЗ на форме тоже 3 строки. Внешний цикл берет первую строку ТЗ. Внутренний цикл пробегает по всем трем строкам документа и записывает в них данные из первой строки ТЗ. Затем внешний цикл берет вторую строку ТЗ, и внутренний цикл снова пробегает по всем трем строкам документа, заменяя данные первой строки ТЗ данными второй. В итоге, после завершения всех итераций, все строки документа будут содержать данные исключительно из последней строки вашей таблицы значений.

Кроме того, получение объекта документа (ПолучитьОбъект()) внутри цикла по строкам — это критическая ошибка производительности. Если в таблице 100 строк, система 100 раз обратится к базе данных за одним и тем же объектом, что значительно замедлит работу.

Решение №1: Полная перезапись табличной части (Самый надежный способ)

Если ваша задача подразумевает, что таблица значений на форме полностью отражает то, как должен выглядеть документ, самым простым и безопасным способом будет полная очистка старой табличной части и добавление новых строк. Рассмотрим, как реализовать это правильно.

В первую очередь нам необходимо сгруппировать данные по документам (если в ТЗ их несколько) и работать с объектом один раз. Рассмотрим пример кода:


// Сначала определим ссылку на документ (если она одна для всей обработки)
ДокументОбъект = СсылкаНаДокумент.ПолучитьОбъект();

// Очищаем существующую табличную часть
ДокументОбъект.Товары.Очистить();

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

// Записываем объект
Попытка
    ДокументОбъект.Записать(РежимЗаписиДокумента.Запись);
    Сообщить("Данные успешно обновлены!");
Исключение
    Сообщить("Ошибка при записи: " + ОписаниеОшибки());
КонецПопытки;

Использование метода ЗаполнитьЗначенияСвойств() позволяет значительно сократить код, если имена реквизитов в ТЗ и в ТЧ документа совпадают.

Решение №2: Обновление существующих строк через поиск

Иногда полная очистка ТЧ недопустима (например, если в строках есть важные уникальные идентификаторы или связи, которые нельзя терять). В этом случае нам нужно найти конкретную строку в документе и обновить её. Проанализируем, как это сделать с помощью метода Найти() или НайтиСтроки().

Рассмотрим алгоритм действий:

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

ВыпускПродукции = Стр.ДокументВыпускПродукции.ПолучитьОбъект();
НашиТовары = ВыпускПродукции.Товары;

Для Каждого Стр Из ТЗ Цикл
    // Ищем строку по ключевому полю, например, по Номенклатуре
    ПараметрыПоиска = Новый Структура("Номенклатура", Стр.Продукция);
    НайденныеСтроки = НашиТовары.НайтиСтроки(ПараметрыПоиска);
    
    Если НайденныеСтроки.Количество() > 0 Тогда
        // Берем первую найденную строку и обновляем ее
        СтрВып = НайденныеСтроки[0];
        СтрВып.Количество = Стр.Количество;
        СтрВып.Цена = Стр.Цена;
        СтрВып.Сумма = Стр.Сумма;
    КонецЕсли;
КонецЦикла;

ВыпускПродукции.Записать();

Важные технические нюансы: почему данные "не видно" после записи?

Выясним причину, по которой пользователь может утверждать: "Я нажал кнопку, код отработал, но в документе ничего не изменилось". Существует несколько факторов, которые часто упускают из виду программисты:

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

2. Подмена данных в обработчиках модуля. Проанализируем ситуацию с процедурами ПередЗаписью и ПриЗаписи в модуле самого документа. Часто там прописаны типовые механизмы (например, пересчет цен или заполнение полей по умолчанию), которые могут "затирать" ваши программно внесенные изменения. Чтобы обойти это, иногда используют флаг ОбменДанными.Загрузка = Истина, однако с этим свойством нужно быть крайне осторожным, так как оно отключает все проверки бизнес-логики.

3. Кеширование и транзакции. Если ваш код выполняется внутри большой транзакции, которая в итоге завершается ошибкой (даже в другом месте), 1С произведет откат (Rollback), и изменения в документе не зафиксируются. Всегда проверяйте Журнал регистрации на наличие скрытых ошибок.

Рекомендации по оптимизации кода

Посмотрим на пример правильной архитектуры функции записи для внешней обработки. Мы будем использовать блокировку объекта, чтобы избежать конфликтов при одновременной работе нескольких пользователей, и вынесем получение объекта за пределы цикла.


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

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

Подведем итог: главная ошибка при работе с ДокументОбъект — это неправильное позиционирование в циклах. Всегда помните, что ПолучитьОбъект() создает в оперативной памяти копию данных из базы. Вы должны изменить эту копию и один раз вызвать метод Записать(). Использование вложенных циклов по всей ТЧ документа внутри цикла по строкам вашей таблицы почти всегда приводит к логической ошибке "последней строки".

← На главную