При разработке торговых систем в 1С часто возникает задача: при изменении одного реквизита в «шапке» документа (например, склада или магазина) необходимо пересчитать данные во всей табличной части. Начинающие разработчики часто сталкиваются с ошибками типизации и производительности при попытке реализовать этот механизм. Разберем по шагам, как правильно организовать автоматическую установку цен во всех строках документа Продажа при выборе магазина, используя возможности платформы 1С 8.3.
Первая проблема, с которой сталкиваются новички — это попытка перебора строк через коллекцию Элементы. Рассмотрим типичный ошибочный код:
&НаКлиенте
Процедура МагазинПриИзменении(Элемент)
Для Каждого СтрокаТЧ Из Элементы.Товары Цикл // ОШИБКА: Итератор для значения не определен
// ... логика расчета
КонецЦикла;
КонецПроцедуры
Проанализируем причину ошибки: в управляемых формах существует строгое разделение. Элементы.Товары — это программный объект визуального контроля (табличное поле на форме). Он отвечает за отображение, сортировку и выделение строк, но сам не является коллекцией данных. Чтобы пройти циклом по строкам, нам необходимо обратиться к данным самого объекта. Правильный путь — Объект.Товары.
Запомним правило: если мы хотим изменить значения в документе (цену, количество, сумму), мы работаем с Объект.ИмяТабличнойЧасти. Если же нам нужно изменить видимость колонки или цвет текста — мы используем Элементы.ИмяЭлемента.
Для хранения цен в нашей задаче используется периодический регистр сведений ЦеныТоваров, подчиненный регистратору. Чтобы получить актуальную цену на дату документа, нам необходимо обратиться к виртуальной таблице «Срез последних».
Рассмотрим, как правильно организовать получение цен. Ошибка многих разработчиков заключается в вызове серверной функции внутри цикла. Если в документе 100 строк, система сделает 100 вызовов сервера, что крайне негативно скажется на скорости работы программы, особенно в «облачных» или клиент-серверных вариантах работы.
Разберем наиболее эффективный алгоритм решения задачи:
СрезПоследних с фильтром по массиву товаров и магазину.Рассмотрим реализацию этого алгоритма в коде формы документа Продажа:
&НаСервере
Функция ПолучитьЦеныДляТоваров(МассивТоваров, ДатаДокумента, Магазин)
РезультатЦен = Новый Соответствие;
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ
| ЦеныТоваровСрезПоследних.Товар КАК Товар,
| ЦеныТоваровСрезПоследних.Цена КАК Цена
|ИЗ
| РегистрСведений.ЦеныТоваров.СрезПоследних(&ДатаДокумента, Магазин = &Магазин И Товар В (&МассивТоваров)) КАК ЦеныТоваровСрезПоследних";
Запрос.УстановитьПараметр("ДатаДокумента", ДатаДокумента);
Запрос.УстановитьПараметр("Магазин", Магазин);
Запрос.УстановитьПараметр("МассивТоваров", МассивТоваров);
Выборка = Запрос.Выполнить().Выбрать();
Пока Выборка.Следующий() Цикл
РезультатЦен.Вставить(Выборка.Товар, Выборка.Цена);
КонецЦикла;
Возврат РезультатЦен;
КонецФункции
&НаКлиенте
Процедура МагазинПриИзменении(Элемент)
Если Объект.Товары.Количество() = 0 Тогда
Возврат;
КонецЕсли;
// 1. Собираем уникальные товары
МассивТоваров = Новый Массив;
Для Каждого Стр Из Объект.Товары Цикл
МассивТоваров.Добавить(Стр.Товар);
КонецЦикла;
// 2. Получаем цены одним вызовом сервера
СоответствиеЦен = ПолучитьЦеныДляТоваров(МассивТоваров, Объект.Дата, Объект.Магазин);
// 3. Обновляем табличную часть
Для Каждого Стр Из Объект.Товары Цикл
НоваяЦена = СоответствиеЦен.Получить(Стр.Товар);
Если НоваяЦена <> Неопределено Тогда
Стр.Цена = НоваяЦена;
// Здесь можно вызвать общую процедуру пересчета суммы
Стр.Сумма = Стр.Количество * Стр.Цена;
КонецЕсли;
КонецЦикла;
КонецПроцедуры
Проанализируем преимущества данного подхода. Во-первых, мы используем объект Соответствие (Mapping). Это позволяет нам мгновенно находить цену для конкретного товара в цикле без необходимости повторного поиска в таблицах значений. Во-вторых, мы минимизировали количество обращений к базе данных до одного запроса. В-третьих, мы учли периодичность регистра — метод СрезПоследних гарантирует получение самой свежей цены на указанный момент времени Объект.Дата.
Обработка пустых значений: Если для товара цена в выбранном магазине не установлена, запрос не вернет строку для этого товара. В коде выше мы использовали проверку Если НоваяЦена <> Неопределено. Это защитит программу от обнуления цен, если они уже были введены вручную, или позволит оставить старую цену, если актуальная не найдена.
Режим записи регистра: Поскольку ваш регистр ЦеныТоваров подчинен регистратору УстановкаЦенМагазина, помните, что записи в нем появляются только при проведении соответствующего документа. Если вы создали запись, но документ не проведен, метод СрезПоследних не увидит эту цену. При тестировании функционала убедитесь, что в базе есть проведенные документы установки цен для выбранного магазина.
Интерфейсная вежливость: Если пользователь уже заполнил документ и случайно сменил магазин, автоматическая перезапись всех цен может привести к потере данных, введенных вручную. Хорошей практикой считается добавление вопроса: "Магазин изменен. Пересчитать цены в табличной части по прайс-листу нового магазина?". Только при подтверждении пользователем стоит запускать процедуру массового обновления.
Используя предложенную методику, вы не только решите текущую задачу Павла, но и создадите надежный, масштабируемый механизм, который будет быстро работать даже при больших объемах данных в табличной части документа.