При разработке программных механизмов заполнения цен в документах разработчики часто сталкиваются с ситуацией, когда кнопка заполнения не выдает ошибок, но цена в табличной части остается пустой или перезаписывается некорректно. Рассмотрим подробнее основные ошибки в исходном коде, проанализируем причины их возникновения и сформируем правильный алгоритм решения задачи.
Проанализируем исходный фрагмент кода, в котором возникла проблема:
&НаКлиенте
Процедура ЗаполнитьЦены(Команда)
Для Каждого строка Из Объект.Запасы Цикл
Цена = ПолучитьЦеныНом(строка.Номенклатура, Объект.Контрагент);
данные = Элементы.Запасы.ТекущиеДанные;
данные.Цена = Цена;
КонецЦикла;
КонецПроцедуры
&НаСервере
Функция ПолучитьЦеныНом(Номенклатура, Контрагент)
отбор = Новый Структура;
отбор.Вставить("Номенклатура", Номенклатура);
отбор.Вставить("ВидЦен", Справочники.ДоговорыКонтрагентов.Выбрать(, Контрагент).ВидЦен);
Возврат РегистрыСведений.ЦеныНоменклатуры.ПолучитьПоследнее(, отбор).Цена;
КонецФункции
В приведенном алгоритме содержится сразу несколько принципиальных ошибок, связанных как с логикой работы с объектами 1С, так и с производительностью управляемого приложения.
Справочники.ДоговорыКонтрагентов.Выбрать(, Контрагент) возвращает объект выборки (СправочникВыборка), а не конкретную ссылку на договор. Попытка напрямую обратиться к реквизиту ВидЦен у выборки возвращает Неопределено. Для обращения к данным выборку необходимо позиционировать методом Следующий().Элементы.Запасы.ТекущиеДанные обращается к текущей выделенной пользователем строке на форме. В цикле Для Каждого значение цены будет многократно перезаписываться только в одной и той же активной строке.ПолучитьЦеныНом на каждом шаге цикла на клиенте приводит к множеству повторных сетевых запросов. При табличной части в несколько десятков или сотен строк это вызовет существенное зависание клиентского сеанса.ПолучитьПоследнее() без указания первого параметра получает срез цен на текущий системный момент времени, а не на дату оформляемого документа.Разберем по шагам, как правильно переписать данный механизм.
Для повышения производительности весь цикл перебора строк и получения цен необходимо выполнять в едином серверном вызове. На клиенте остается только команда вызова процедур.
Вместо обхода выборки договора правильнее обращаться к договору, указанному непосредственно в шапке документа (например, Объект.ДоговорКонтрагента), либо считывать вид цен через прямой реквизит договора. При получении цены из регистра сведений следует обязательно передавать дату документа Объект.Дата.
Рассмотрим правильный вариант реализации с переносом логики на сервер:
&НаКлиенте
Процедура ЗаполнитьЦены(Команда)
ЗаполнитьЦеныНаСервере();
КонецПроцедуры
&НаСервере
Процедура ЗаполнитьЦеныНаСервере()
// Проверяем заполнение договора в документе
Если НЕ ЗначениеЗаполнено(Объект.ДоговорКонтрагента) Тогда
Возврат;
КонецЕсли;
ВидЦен = Объект.ДоговорКонтрагента.ВидЦен;
Отбор = Новый Структура;
Отбор.Вставить("ВидЦен", ВидЦен);
Для Каждого СтрокаТЧ Из Объект.Запасы Цикл
Отбор.Вставить("Номенклатура", СтрокаТЧ.Номенклатура);
// Получаем структуру с последними значениями реквизитов регистра на дату документа
СтруктураЦены = РегистрыСведений.ЦеныНоменклатуры.ПолучитьПоследнее(Объект.Дата, Отбор);
СтрокаТЧ.Цена = СтруктураЦены.Цена;
КонецЦикла;
КонецПроцедуры
Поштучный вызов метода ПолучитьПоследнее() внутри цикла даже на сервере создает лишние обращения к базе данных. Наиболее оптимальным решением является использование запроса к виртуальной таблице РегистрСведений.ЦеныНоменклатуры.СрезПоследних.
Посмотрим на пример заполнения цен единым запросом:
&НаСервере
Процедура ЗаполнитьЦеныЗапросомНаСервере()
ВидЦен = Объект.ДоговорКонтрагента.ВидЦен;
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ
| Запасы.Номенклатура КАК Номенклатура
|ПОМЕСТИТЬ ВТ_Номенклатура
|ИЗ
| &ТЧЗапасы КАК Запасы
|;
|
|ВЫБРАТЬ
| ВТ.Номенклатура КАК Номенклатура,
| ЕСТЬNULL(Цены.Цена, 0) КАК Цена
|ИЗ
| ВТ_Номенклатура КАК ВТ
| ЛЕВОЕ СОЕДИНЕНИЕ РегистрСведений.ЦеныНоменклатуры.СрезПоследних(&Период, ВидЦен = &ВидЦен) КАК Цены
| ПО ВТ.Номенклатура = Цены.Номенклатура";
Запрос.УстановитьПараметр("Период", Объект.Дата);
Запрос.УстановитьПараметр("ВидЦен", ВидЦен);
Запрос.УстановитьПараметр("ТЧЗапасы", Объект.Запасы.Выгрузить());
Результат = Запрос.Выполнить().Выгрузить();
Для Каждого СтрокаТЧ Из Объект.Запасы Цикл
НайденнаяСтрока = Результат.Найти(СтрокаТЧ.Номенклатура, "Номенклатура");
Если НайденнаяСтрока <> Неопределено Тогда
СтрокаТЧ.Цена = НайденнаяСтрока.Цена;
КонецЕсли;
КонецЦикла;
КонецПроцедуры
Если при попытке отладки точка останова внутри процедуры с директивой &НаСервере проскакивает и не срабатывает, необходимо проверить настройки сервера 1С:Предприятие:
1C:Enterprise 8.3 Server Daemon должен быть установлен ключ запуска -debug.Соблюдение правил работы с выборками справочников, корректный доступ к строкам табличной части и запуск тяжелых операций на сервере позволяют гарантировать правильное и быстрое заполнение цен в документах 1С.