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

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

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

Анализ проблемы и разбор типичных ошибок

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


&НаСервере
Процедура ДобавлениеКоличества
    Запрос = Новый Запрос;
    Запрос.Текст = "ВЫБРАТЬ | Покупки.Ссылка |ИЗ | Документ.Покупки КАК Покупки |ГДЕ | Покупки.Комментарий = &Комментарий";
    Запрос.УстановитьПараметр("Комментарий", "Автомат");
    Выборка = Запрос.Выполнить.Выбрать;
    Пока Выборка.Следующий Цикл
        ОбПокупка = Выборка.Ссылка.ПолучитьОбъект;
        Для Каждого Строка Из Выборка.Ссылка.ТабличнаяЧасть4 Цикл
            Колич = Строка.Количество;
            Если Колич > 0 Тогда
                Продолжить Иначе
                Колич = ("1,000")
            КонецЕсли;
        КонецЦикла;
        Сообщить("Создается " + ОбПокупка);
        Сообщить(Колич);
    КонецЦикла;
    ОбПокупка.Записать;
    Сообщить("Создание завершено!");
КонецПроцедуры

В этом небольшом фрагменте кода скрыто сразу несколько серьезных ошибок, из-за которых алгоритм работает некорректно. Разберем их по шагам:

  1. Обход табличной части по ссылке, а не по объекту. В строке Для Каждого Строка Из Выборка.Ссылка.ТабличнаяЧасть4 обход идет по коллекции ссылки. Ссылка (ДокументСсылка) предназначена только для чтения данных. Даже если мы попытаемся изменить свойства внутри этого цикла, платформа проигнорирует изменения или выдаст ошибку, так как для редактирования данных необходимо использовать полученный объект ОбПокупка.
  2. Присвоение значения локальной переменной вместо реквизита строки. Конструкция Колич = Строка.Количество; Колич = ("1,000") лишь перезаписывает значение во временной переменной Колич. Реальное значение в строке табличной части документа при этом не меняется. Чтобы изменить значение, необходимо обращаться напрямую к реквизиту строки: Строка.Количество = 1;.
  3. Вызов метода записи вне цикла. Строка ОбПокупка.Записать; находится за пределами цикла Пока Выборка.Следующий(). В результате выполнения этого кода запишется только самый последний документ из выборки (и то без изменений в табличной части из-за первых двух ошибок), а все предыдущие документы вообще не будут изменены.
  4. Опасность неявного приведения типов. Попытка присвоить числовому реквизиту строковое значение "1,000" может привести к непредсказуемому поведению. В 1С работает неявное приведение типов, но использование запятой в качестве разделителя зависит от региональных настроек сервера. Правильно использовать чистое числовое значение: 1 или 1.0.

Исправление базовых ошибок в коде

Для того чтобы исправить базовые ошибки и заставить код выполнять свою задачу, нам необходимо переориентировать цикл на работу с объектом, записывать изменения внутри цикла выборки и напрямую обращаться к реквизитам строк табличной части.

Рассмотрим исправленный вариант базового алгоритма:


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

Профессиональное решение и оптимизация (Стандарты 1С)

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

1. Оптимизация запроса (Избавление от лишних операций ПолучитьОбъект)

Метод ПолучитьОбъект() — одна из самых тяжелых операций в платформе 1С. Она считывает из базы данных всю информацию о документе, включая все его реквизиты и все табличные части. В исходном коде мы считываем абсолютно все документы с комментарием "Автомат", даже если у них все в порядке с количеством.

Правильным решением будет перенести фильтрацию на уровень СУБД с помощью запроса. Мы должны выбирать только те ссылки документов, у которых в табличной части действительно есть строки с нулевым или незаполненным количеством. Это позволит избежать лишней работы сервера:


Запрос.Текст = 
    "ВЫБРАТЬ РАЗЛИЧНЫЕ
    |	ПокупкиТабЧасть.Ссылка КАК Ссылка
    |ИЗ
    |	Документ.Покупки.ТабличнаяЧасть4 КАК ПокупкиТабЧасть
    |ГДЕ
    |	ПокупкиТабЧасть.Ссылка.Комментарий = &Комментарий
    |	И (ПокупкиТабЧасть.Количество = 0 ИЛИ ПокупкиТабЧасть.Количество ЕСТЬ NULL)";

Использование ключевого слова РАЗЛИЧНЫЕ гарантирует, что каждая ссылка на документ встретится в выборке только один раз, даже если в документе несколько дефектных строк.

2. Корректная обработка проведения документов

Если изменяемый документ уже был проведен в системе (Ссылка.Проведен = Истина), то простая перезапись через метод Записать() может привести к рассинхронизации данных. Документ в базе изменится, но движения по регистрам останутся старыми. Чтобы этого избежать, необходимо программно анализировать статус проведения документа и перезаписывать его в соответствующем режиме:


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

3. Безопасность в многопользовательской среде (Управляемые блокировки)

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

Итоговый отказоустойчивый и оптимизированный код

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


&НаСервере
Процедура ВыполнитьМассовоеОбновлениеКоличества()
    
    // Шаг 1. Оптимальный запрос к СУБД
    Запрос = Новый Запрос;
    Запрос.Текст = 
        "ВЫБРАТЬ РАЗЛИЧНЫЕ
        |	ПокупкиТабличнаяЧасть4.Ссылка КАК Ссылка
        |ИЗ
        |	Документ.Покупки.ТабличнаяЧасть4 КАК ПокупкиТабличнаяЧасть4
        |ГДЕ
        |	ПокупкиТабличнаяЧасть4.Ссылка.Комментарий = &Комментарий
        |	И (ПокупкиТабличнаяЧасть4.Количество = 0 ИЛИ ПокупкиТабличнаяЧасть4.Количество ЕСТЬ NULL)";
        
    Запрос.УстановитьПараметр("Комментарий", "Автомат");
    
    Выборка = Запрос.Выполнить().Выбрать();
    
    Пока Выборка.Следующий() Цикл
        
        // Шаг 2. Установка управляемой блокировки на документ
        Блокировка = Новый БлокировкаДанных;
        ЭлементБлокировки = Блокировка.Добавить("Документ.Покупки");
        ЭлементБлокировки.УстановитьПараметр("Ссылка", Выборка.Ссылка);
        ЭлементБлокировки.Режим = РежимБлокировкиДанных.Исключительный;
        
        Попытка
            Блокировка.Заблокировать();
            
            // Шаг 3. Получение объекта
            ОбПокупка = Выборка.Ссылка.ПолучитьОбъект();
            
            // Шаг 4. Модификация табличной части объекта
            Для Каждого Строка Из ОбПокупка.ТабличнаяЧасть4 Цикл
                Если Строка.Количество = 0 Тогда
                    Строка.Количество = 1; // Используем строго числовой тип данных
                КонецЕсли;
            КонецЦикла;
            
            // Шаг 5. Запись документа с учетом его статуса проведения
            Если ОбПокупка.Проведен Тогда
                ОбПокупка.Записать(РежимЗаписиДокумента.Проведение);
            Иначе
                ОбПокупка.Записать(РежимЗаписиДокумента.Запись);
            КонецЕсли;
            
            Сообщить("Успешно обработан документ: " + ОбПокупка.Ссылка);
            
        Исключение
            // Шаг 6. Надежная обработка ошибок без прерывания всего процесса
            ЗаписьЖурналаРегистрации("МассовоеИзменениеКоличества",
                УровеньЖурналаРегистрации.Ошибка,
                Метаданные.Документы.Покупки,
                Выборка.Ссылка,
                "Ошибка обработки документа: " + ПодробноеПредставлениеОшибки(ИнформацияОбОшибке()));
                
            Сообщить("Не удалось обработать документ: " + Выборка.Ссылка + ". Подробности в Журнале регистрации.");
        КонецПопытки;
        
    КонецЦикла;
    
    Сообщить("Массовая обработка документов успешно завершена!");
    
КонецПроцедуры

Альтернативное решение: Использование типовых инструментов 1С

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

Рассмотрим, как решить эту задачу без программирования:

  1. Перейдите в раздел Администрирование -> Обслуживание -> Корректировка данных и откройте инструмент «Групповое изменение реквизитов» (в некоторых конфигурациях можно просто выделить нужные документы в списке, нажать правую кнопку мыши и выбрать пункт «Изменить выделенные...»).
  2. В поле «Изменять» выберите тип документа Покупки.
  3. Нажмите гиперссылку «Добавить условие отбора» и настройте фильтрацию: Комментарий Равно "Автомат".
  4. В списке реквизитов найдите и раскройте вашу табличную часть (например, Табличная часть 4).
  5. Найдите колонку Количество, установите галочку активности, выберите действие «Установить значение» или используйте формулу для замены нулевых значений на единицу.
  6. Нажмите кнопку «Изменить реквизиты». Платформа самостоятельно, безопасно и с соблюдением всех транзакций и блокировок обновит выбранные документы.
← На главную