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

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

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

Различие между данными и элементами формы: устраняем ошибку итератора

Первая проблема, с которой сталкиваются новички — это попытка перебора строк через коллекцию Элементы. Рассмотрим типичный ошибочный код:


&НаКлиенте
Процедура МагазинПриИзменении(Элемент)
    Для Каждого СтрокаТЧ Из Элементы.Товары Цикл // ОШИБКА: Итератор для значения не определен
        // ... логика расчета
    КонецЦикла;
КонецПроцедуры

Проанализируем причину ошибки: в управляемых формах существует строгое разделение. Элементы.Товары — это программный объект визуального контроля (табличное поле на форме). Он отвечает за отображение, сортировку и выделение строк, но сам не является коллекцией данных. Чтобы пройти циклом по строкам, нам необходимо обратиться к данным самого объекта. Правильный путь — Объект.Товары.

Запомним правило: если мы хотим изменить значения в документе (цену, количество, сумму), мы работаем с Объект.ИмяТабличнойЧасти. Если же нам нужно изменить видимость колонки или цвет текста — мы используем Элементы.ИмяЭлемента.

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

Для хранения цен в нашей задаче используется периодический регистр сведений ЦеныТоваров, подчиненный регистратору. Чтобы получить актуальную цену на дату документа, нам необходимо обратиться к виртуальной таблице «Срез последних».

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

Оптимальное решение: использование запроса и временных коллекций

Разберем наиболее эффективный алгоритм решения задачи:

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

Рассмотрим реализацию этого алгоритма в коде формы документа Продажа:


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

&НаКлиенте
Процедура МагазинПриИзменении(Элемент)
    
    Если Объект.Товары.Количество() = 0 Тогда
        Возврат;
    КонецЕсли;
    
    // 1. Собираем уникальные товары
    МассивТоваров = Новый Массив;
    Для Каждого Стр Из Объект.Товары Цикл
        МассивТоваров.Добавить(Стр.Товар);
    КонецЦикла;
    
    // 2. Получаем цены одним вызовом сервера
    СоответствиеЦен = ПолучитьЦеныДляТоваров(МассивТоваров, Объект.Дата, Объект.Магазин);
    
    // 3. Обновляем табличную часть
    Для Каждого Стр Из Объект.Товары Цикл
        НоваяЦена = СоответствиеЦен.Получить(Стр.Товар);
        Если НоваяЦена <> Неопределено Тогда
            Стр.Цена = НоваяЦена;
            // Здесь можно вызвать общую процедуру пересчета суммы
            Стр.Сумма = Стр.Количество * Стр.Цена;
        КонецЕсли;
    КонецЦикла;
    
КонецПроцедуры

Почему это решение лучше?

Проанализируем преимущества данного подхода. Во-первых, мы используем объект Соответствие (Mapping). Это позволяет нам мгновенно находить цену для конкретного товара в цикле без необходимости повторного поиска в таблицах значений. Во-вторых, мы минимизировали количество обращений к базе данных до одного запроса. В-третьих, мы учли периодичность регистра — метод СрезПоследних гарантирует получение самой свежей цены на указанный момент времени Объект.Дата.

Дополнительные рекомендации

Обработка пустых значений: Если для товара цена в выбранном магазине не установлена, запрос не вернет строку для этого товара. В коде выше мы использовали проверку Если НоваяЦена <> Неопределено. Это защитит программу от обнуления цен, если они уже были введены вручную, или позволит оставить старую цену, если актуальная не найдена.

Режим записи регистра: Поскольку ваш регистр ЦеныТоваров подчинен регистратору УстановкаЦенМагазина, помните, что записи в нем появляются только при проведении соответствующего документа. Если вы создали запись, но документ не проведен, метод СрезПоследних не увидит эту цену. При тестировании функционала убедитесь, что в базе есть проведенные документы установки цен для выбранного магазина.

Интерфейсная вежливость: Если пользователь уже заполнил документ и случайно сменил магазин, автоматическая перезапись всех цен может привести к потере данных, введенных вручную. Хорошей практикой считается добавление вопроса: "Магазин изменен. Пересчитать цены в табличной части по прайс-листу нового магазина?". Только при подтверждении пользователем стоит запускать процедуру массового обновления.

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

← На главную