В розничной торговле крайне популярна механика «двойных ценников», когда покупателю наглядно демонстрируют старую (обычную) цену и новую (акционную). При реализации этой задачи в программе 1С:Управление нашей фирмой (УНФ) или 1С:Розница 3.0 разработчики часто сталкиваются с трудностями извлечения цен разных видов для одной и той же номенклатуры.
В этой статье мы подробно разберем, как вывести акционную цену на ценник. Мы проанализируем стандартные возможности конфигурации, которые не требуют написания кода, а также подробно рассмотрим правильный программный метод получения цен из регистров сведений без ущерба для производительности системы.
Прежде чем приступать к доработке программного кода, важно проверить, решает ли конфигурация задачу стандартными средствами. Разработчики «1С:УНФ» уже предусмотрели функционал для печати двойных ценников. Если вам не требуется кардинально менять логику компоновки данных, выполните следующие шаги:
Такой подход экономит время на поддержку конфигурации при обновлениях и полностью решает задачу в рамках типового функционала.
Рассмотрим ситуацию, когда печать ценников глубоко кастомизирована, используется нестандартный макет, и данные необходимо получить строго в коде внешней обработки или расширения.
На форумах часто предлагают использовать метод ПолучитьПоследнее() менеджера регистров сведений. Код выглядит следующим образом:
// НЕОПТИМАЛЬНЫЙ КОД (Запрос в цикле)
Для Каждого а Из Объект.Товары Цикл
обл = мак.ПолучитьОбласть("Стр1|База");
обл.Параметры.Имя = а.Номенклатура.НаименованиеПолное;
обл.Параметры.Цена = а.Цена; // Обычная цена
// Получение акционной цены через ПолучитьПоследнее
Отбор = Новый Структура;
Отбор.Вставить("ВидЦен", Справочники.ВидыЦен.НайтиПоНаименованию("Акционная цена"));
Отбор.Вставить("Номенклатура", а.Номенклатура);
АктуальнаяЦена = РегистрыСведений.ЦеныНоменклатуры.ПолучитьПоследнее(Объект.Дата, Отбор);
обл.Параметры.ЦенаАкция = АктуальнаяЦена.Цена;
КонецЦикла;
В чем критический недостаток этого кода? Это классическая ошибка «Запрос в цикле» (паттерн N+1). Метод ПолучитьПоследнее() под капотом обращается напрямую к базе данных SQL. Если пользователь решит распечатать ценники для 200–300 позиций номенклатуры одновременно, система совершит 300 отдельных транзакций к СУБД. Это приведет к сильному зависанию интерфейса и избыточной нагрузке на сервер.
Чтобы программа работала мгновенно даже при печати тысяч ценников, мы применим правильный алгоритм работы с данными в 1С:
СрезПоследних регистра сведений ЦеныНоменклатуры с фильтром по нашему массиву товаров.Соответствие (карта соответствий).Рассмотрим детальную реализацию этого алгоритма:
Если Объект.Товары.Количество() > 0 Тогда
// Шаг 1. Формируем массив уникальных товаров для отбора в запросе
МассивНоменклатуры = Новый Массив;
Для Каждого СтрокаТовара Из Объект.Товары Цикл
// Исключаем дубли для оптимизации запроса
Если МассивНоменклатуры.Найти(СтрокаТовара.Номенклатура) = Неопределено Тогда
МассивНоменклатуры.Добавить(СтрокаТовара.Номенклатура);
КонецЕсли;
КонецЦикла;
// Шаг 2. Находим нужный вид цен
АкционныйВидЦен = Справочники.ВидыЦен.НайтиПоНаименованию("Акционная цена");
Если НЕ ЗначениеЗаполнено(АкционныйВидЦен) Тогда
ВызватьИсключение "Не найден вид цен 'Акционная цена' в справочнике!";
КонецЕсли;
// Шаг 3. Строим пакетный запрос к регистру ЦеныНоменклатуры
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ
| ЦеныНоменклатурыСрезПоследних.Номенклатура КАК Номенклатура,
| ЦеныНоменклатурыСрезПоследних.Характеристика КАК Характеристика,
| ЦеныНоменклатурыСрезПоследних.Цена КАК Цена
|ИЗ
| РегистрСведений.ЦеныНоменклатуры.СрезПоследних(
| &Период,
| ВидЦен = &ВидЦен
| И Номенклатура В (&МассивНоменклатуры)
| ) КАК ЦеныНоменклатурыСрезПоследних";
Запрос.УстановитьПараметр("Период", Объект.Дата);
Запрос.УстановитьПараметр("ВидЦен", АкционныйВидЦен);
Запрос.УстановитьПараметр("МассивНоменклатуры", МассивНоменклатуры);
ТаблицаЦен = Запрос.Выполнить().Выгрузить();
// Шаг 4. Создаем Соответствие для быстрого поиска по ключу Номенклатура + Характеристика
// В качестве ключа будем использовать структуру или уникальный идентификатор строки
СоответствиеЦен = Новый Соответствие;
Для Каждого СтрокаЦены Из ТаблицаЦен Цикл
// Создаем уникальный ключ, учитывающий характеристику
КлючПоиска = Новый Структура("Номенклатура, Характеристика", СтрокаЦены.Номенклатура, СтрокаЦены.Характеристика);
СоответствиеЦен.Вставить(КлючПоиска, СтрокаЦены.Цена);
КонецЦикла;
// Шаг 5. Вывод данных на печать без обращений к СУБД в цикле
мак = ПолучитьОбщийМакет("ТомаковкаF5Акционный");
Для Каждого а Из Объект.Товары Цикл
обл = мак.ПолучитьОбласть("Стр1|База");
обл.Параметры.Имя = а.Номенклатура.НаименованиеПолное;
обл.Параметры.Цена = а.Цена; // Базовая цена из обработки
// Формируем ключ для поиска цены текущей строки
ТекущийКлюч = Новый Структура("Номенклатура, Характеристика", а.Номенклатура, а.Характеристика);
НайденнаяЦена = СоответствиеЦен.Получить(ТекущийКлюч);
// Если акционная цена не задана, выводим 0 или оставляем поле пустым
обл.Параметры.ЦенаАкция = ?(НайденнаяЦена = Неопределено, 0, НайденнаяЦена);
// Здесь продолжается вывод области в табличный документ...
КонецЦикла;
КонецЕсли;
Обратите внимание на то, как в третьем способе реализована работа с измерениями. В конфигурациях 1С:УНФ и 1С:Розница учет товаров практически всегда ведется с использованием характеристик (размер, цвет, объем памяти и т.д.). Цены в периодическом регистре ЦеныНоменклатуры могут отличаться для разных характеристик одного товара.
Если при получении цен игнорировать измерение Характеристика, вы рискуете вывести некорректные цены на печать или получить пустые значения. В представленном выше коде для этого создается составной ключ Номенклатура + Характеристика, гарантирующий стопроцентную точность сопоставления цен.
Для простых задач вывода цен всегда используйте встроенный интерактивный механизм «Двойных ценников» в УНФ. Если же вы создаете кастомное программное решение, всегда помните о производительности системы и избегайте вызовов метода ПолучитьПоследнее() внутри циклов — собирайте данные одним запросом.