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

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

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

Анализ исходной проблемы: почему код работает медленно или неверно

Проанализируем типичную ситуацию, представленную в вопросе автора. Основная проблема заключалась в том, что при наличии нескольких строк в документе все они принимали цену последнего найденного товара. Кроме того, использовался запрос внутри цикла, что является грубейшим нарушением стандартов разработки 1С.

Разберем основные ошибки:

  1. Запрос внутри цикла по строкам: Каждый раз, когда выполняется Запрос2.Выполнить() внутри цикла Для каждого Строка Из ДокументОбъект.Товары, система обращается к базе данных. Если у вас 100 документов по 50 строк в каждом, это даст 5000 лишних обращений к СУБД.
  2. Получение объекта внутри выборки: Метод ПолучитьОбъект() — это «тяжелая» операция. Если вызывать его для каждой строки, система будет повторно считывать весь документ из базы, даже если вы только что его обрабатывали.
  3. Отсутствие параметров в Срезе Последних: Обращение к виртуальной таблице РегистрСведений.ЦеныНоменклатуры.СрезПоследних без указания даты и фильтров заставляет систему анализировать весь регистр, что замедляет работу.
  4. Логическая ошибка обновления: Если в документе несколько строк с ценой «1», но запрос в цикле возвращает данные только по последней найденной номенклатуре, это приводит к перезаписи всех строк неверными значениями.

Решение №1: Оптимизированный подход с использованием одного запроса

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

Рассмотрим пример кода, реализующий эту логику:


Запрос = Новый Запрос;
Запрос.Текст = 
    "ВЫБРАТЬ
    |    Док.Ссылка КАК Ссылка,
    |    Док.Номенклатура КАК Номенклатура,
    |    ЕСТЬNULL(Цены.Цена, 0) КАК НоваяЦена
    |ИЗ
    |    Документ.ПередачаТоваров.Товары КАК Док
    |        ЛЕВОЕ СОЕДИНЕНИЕ РегистрСведений.ЦеныНоменклатуры.СрезПоследних(&Период, ТипЦены = &ТипЦены) КАК Цены
    |        ПО Док.Номенклатура = Цены.Номенклатура
    |ГДЕ
    |    Док.Ссылка.ВидОперации = ЗНАЧЕНИЕ(Перечисление.ВидыОперацийПередачаТоваров.БезвозмезднаяПередача)
    |    И Док.Цена = 1
    |    И Док.Ссылка.ПометкаУдаления = ЛОЖЬ
    |ИТОГИ ПО
    |    Ссылка";

Запрос.УстановитьПараметр("Период", ТекущаяДата()); // Или дата документа
Запрос.УстановитьПараметр("ТипЦены", НужныйТипЦены);

РезультатЗапроса = Запрос.Выполнить();
ВыборкаДокумент = РезультатЗапроса.Выбрать(ОбходРезультатаЗапроса.ПоГруппировкам);

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

В этом примере мы используем конструкцию ИТОГИ ПО Ссылка. Это позволяет нам один раз получить объект документа, обработать все его строки по данным из выборки и один раз сохранить изменения.

Решение №2: Использование «Таблицы соответствия» (для сложных алгоритмов)

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

Разберем алгоритм по шагам:

  1. Сначала выберем все уникальные ссылки на документы, которые требуют обработки.
  2. Для этих документов соберем массив всей номенклатуры.
  3. Одним запросом получим цены для всего массива номенклатуры и поместим их в Соответствие, где ключом будет Номенклатура, а значением — Цена.
  4. При обходе строк документа будем просто брать цену из этого «кэша» в памяти.

// Шаг 1: Получаем цены заранее
ЦеныКэш = Новый Соответствие;
ЗапросЦен = Новый Запрос("ВЫБРАТЬ Номенклатура, Цена ИЗ РегистрСведений.ЦеныНоменклатуры.СрезПоследних(&Дата, ТипЦены = &ТипЦены)");
ЗапросЦен.УстановитьПараметр("Дата", ТекущаяДата());
ЗапросЦен.УстановитьПараметр("ТипЦены", Справочники.ТипыЦенНоменклатуры.НайтиПоНаименованию("Оптовая"));

ВыборкаЦен = ЗапросЦен.Выполнить().Выбрать();
Пока ВыборкаЦен.Следующий() Цикл
    ЦеныКэш.Вставить(ВыборкаЦен.Номенклатура, ВыборкаЦен.Цена);
КонецЦикла;

// Шаг 2: Обработка документов
ВыборкаДок = Справочники.ПередачаТоваров.Выбрать(); // Условно
Пока ВыборкаДок.Следующий() Цикл
    // Логика проверки условий и заполнения из ЦеныКэш.Получить(Строка.Номенклатура)
КонецЦикла;

Решение №3: Использование типовых API (БСП и типовые конфигурации)

В современных конфигурациях, таких как УТ 11, ERP или БП 3.0, разработчики фирмы «1С» уже подготовили оптимизированные функции. Например, в модуле Ценообразование часто присутствуют методы для пакетного получения цен.

Посмотрим на пример использования типовой функции:


// В типовых конфигурациях часто доступен программный интерфейс:
СтруктураПараметров = ЦенообразованиеКлиентСервер.ПараметрыЗаполненияЦен();
СтруктураПараметров.Дата = ТекущаяДата();
СтруктураПараметров.ТипЦен = Объект.ТипЦен;

// Вызов серверного метода для получения таблицы цен сразу на весь список товаров
ТаблицаЦен = ЦенообразованиеВызовСервера.ПолучитьЦеныНоменклатуры(МассивТоваров, СтруктураПараметров);

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

Важные рекомендации по работе с объектами данных

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

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

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

← На главную