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