Разработчики, приходящие в среду «1С:Предприятие 7.7» из более современных версий платформы (таких как 1С 8.3), часто сталкиваются с трудностями при попытке получить актуальные розничные цены. В платформе 1С 8.х задача получения цен решается в одну строчку через срез последних виртуальной таблицы регистра сведений. В «семерке» же аналогичного простого механизма нет, а сама архитектура розничного учета имеет ряд критически важных особенностей.
В этой статье мы подробно разберем, как устроен учет розничных цен в типовой конфигурации «Торговля и склад 7.7» (ред. 9.2), почему возникают расхождения между справочниками и регистрами, а также рассмотрим четыре практических способа решения этой задачи с примерами кода.
---
Для начала проанализируем, где именно в системе хранятся цены и почему у одного и того же товара на разных складах розничная цена может отличаться, даже если для них задан один и тот же тип цен.
В «1С:Торговля и склад 7.7» учет на складах с признаком «Розничный» ведется принципиально иначе, чем на оптовых. Ключевые особенности этой схемы:
ОстаткиТМЦ остатки товаров на розничных складах хранятся не только в количественном выражении, но и в разрезе конкретных цен продажи. Для этого в регистре предусмотрено специальное измерение — ЦенаПрод.Цены, стоимость остатков товара на розничном складе автоматически не изменится. Чтобы новая розничная цена вступила в силу, необходимо обязательно создать и провести документ Переоценка товаров в розницу. Этот документ списывает остатки по старой цене продажи и приходует их по новой цене из справочника.Чек ККМ или Реализация (розница)), система берет цену продажи исключительно из регистра ОстаткиТМЦ (измерение ЦенаПрод). Продать товар по цене из справочника, если она не совпадает с регистром, стандартными средствами розницы невозможно.Именно поэтому возникает ситуация, когда цена в справочнике («бумажная» цена) не совпадает с реальной ценой продажи на конкретной торговой точке (зафиксированной в регистре).
---
Выясним основные причины, из-за которых розничные цены в справочнике и регистре начинают различаться:
ОстаткиТМЦ обнуляется и исчезает. Стандартный запрос к регистру остатков не вернет этот товар, хотя в справочнике Цены розничная цена для него установлена и актуальна.ОстаткиТМЦ для старой партии осталась прежняя ЦенаПрод.Ниже мы подробно разберем четыре практических метода получения цен, каждый из которых подходит под свои задачи.
---
Если перед нами стоит задача получить цены только тех товаров, которые реально есть на остатках розничного склада, мы должны использовать запрос к регистру ОстаткиТМЦ. Это гарантирует, что мы получим именно ту цену, по которой товар будет пробиваться на кассе.
Напишем стандартный запрос «семерки» для получения остатков товаров в разрезе складов и цен продажи:
Процедура ПолучитьЦеныИзРегистра()
Перем Запрос, ТекстЗапроса, ТабЗнач;
Запрос = СоздатьОбъект("Запрос");
ТекстЗапроса =
"//{{ЗАПРОС(РозничныеЦеныРегистр)
|Период с РабочаяДата() по РабочаяДата();
|Номенклатура = Регистр.ОстаткиТМЦ.Номенклатура;
|Склад = Регистр.ОстаткиТМЦ.Склад;
|ЦенаПрод = Регистр.ОстаткиТМЦ.ЦенаПрод;
|Количество = Регистр.ОстаткиТМЦ.Количество;
|Функция КолОст = КонОст(Количество);
|Группировка Склад;
|Группировка Номенклатура без групп;
|Группировка ЦенаПрод;
|Условие(Склад.ВидСклада = Перечисление.ВидыСкладов.Розничный);
|Условие(Количество > 0);
|//}}ЗАПРОС"
;
Если Запрос.Выполнить(ТекстЗапроса) = 0 Тогда
Предупреждение("Ошибка выполнения запроса по регистру ОстаткиТМЦ!");
Возврат;
КонецЕсли;
// Выгружаем результаты в таблицу значений для последующего анализа
ТабЗнач = СоздатьОбъект("ТаблицаЗначений");
Запрос.Выгрузить(ТабЗнач, 1, 0);
// Выведем результат в окно сообщений
ТабЗнач.ВыбратьСтроки();
Пока ТабЗнач.ПолучитьСтроку() = 1 Цикл
Сообщить("Склад: " + ТабЗнач.Склад + " | " +
"Товар: " + ТабЗнач.Номенклатура + " | " +
"Цена в регистре: " + ТабЗнач.ЦенаПрод + " | " +
"Остаток: " + ТабЗнач.КолОст);
КонецЦикла;
КонецПроцедуры
Преимущества метода: Показывает реальные цены, по которым сейчас осуществляются продажи в розничных точках.
Недостатки: Не выводит товары, у которых нулевой остаток на складе.
---
Если нам нужно составить прайс-лист или получить цены на товары вне зависимости от их физического наличия на складе, необходимо обращаться к справочнику Цены. Поля цен в 1С 7.7 являются периодическими реквизитами.
Для получения единичных значений цен на определенную дату мы можем воспользоваться встроенным объектом Периодический или стандартной глобальной функцией конфигурации ТиС глПолучитьЦену().
Рассмотрим пример обхода справочника с получением цен на определенную дату:
Процедура ПолучитьЦеныИзСправочника()
Перем СпрНом, СпрЦены, ОбъектПериодический, ВыбТипЦен, АктуальнаяЦена;
СпрНом = СоздатьОбъект("Справочник.Номенклатура");
СпрЦены = СоздатьОбъект("Справочник.Цены");
ОбъектПериодический = СоздатьОбъект("Периодический");
// Предположим, у нас есть выбранный тип цен (например, розничные)
ВыбТипЦен = Константа.РозничныйТипЦен;
СпрНом.ВыбратьЭлементы();
Пока СпрНом.ПолучитьЭлемент() = 1 Цикл
// Пропускаем группы номенклатуры
Если СпрНом.ЭтоГруппа() = 1 Тогда
Продолжить;
КонецЕсли;
// Настраиваем выборку подчиненного справочника Цены
СпрЦены.ИспользоватьВладельца(СпрНом.ТекущийЭлемент());
СпрЦены.ВыбратьЭлементы();
Пока СпрЦены.ПолучитьЭлемент() = 1 Цикл
// Ищем только цены нужного нам типа
Если СпрЦены.ТипЦен = ВыбТипЦен Тогда
// Инициализируем периодический объект для получения значения на дату
ОбъектПериодический.ИспользоватьОбъект("Цена", СпрЦены.ТекущийЭлемент());
АктуальнаяЦена = ОбъектПериодический.ЗначениеНаДату(РабочаяДата());
Сообщить("Товар: " + СпрНом.Наименование + " | " +
"Тип цен: " + ВыбТипЦен + " | " +
"Цена из справочника: " + АктуальнаяЦена);
КонецЕсли;
КонецЦикла;
КонецЦикла;
КонецПроцедуры
Важное предупреждение о производительности: Использование циклов с обращением к периодическим реквизитам через объект Периодический или функцию глПолучитьЦену() для большого объема данных (более 1000 позиций номенклатуры) работает крайне медленно. Происходит так называемая проблема "N+1 запросов", когда база данных перегружается огромным количеством мелких одиночных обращений. Для построения больших прайс-листов этот способ не подходит.
---
Если база данных работает под управлением Microsoft SQL Server (или используется DBF-версия с установленным драйвером SQLite), оптимальным решением для быстрого среза последних цен является использование внешней компоненты 1С++ (1C-Плюс-Плюс).
С помощью класса ПрямойЗапрос мы можем обратиться к SQL-таблицам напрямую. В компоненте реализована мощнейшая виртуальная функция $СрезПоследних, которая мгновенно собирает актуальные периодические реквизиты цен из таблицы истории значений (_1sconst) по всему справочнику.
Проанализируем пример построения прямого запроса для получения цен:
// Пример концептуального построения прямого запроса с помощью 1С++
Процедура ПолучитьЦеныЧерезПрямойЗапрос()
Перем ЗапросSQL, ТекстЗапроса, ТабРезультат;
ЗапросSQL = СоздатьОбъект("ПрямойЗапрос");
// Формируем SQL-запрос с виртуальной таблицей $СрезПоследних
ТекстЗапроса = "
|SELECT
| спрНом.descr as Товар,
| срезЦен.Цена as РозничнаяЦена
|FROM
| $Справочник.Номенклатура as спрНом
|INNER JOIN
| $Справочник.Цены as спрЦены ON спрЦены.parentid = спрНом.id
|LEFT JOIN
| $Периодический.Цена.СрезПоследних(:ВыбДата, ) as срезЦен ON срезЦен.objid = спрЦены.id
|WHERE
| спрЦены.$ТипЦен = :ВыбТипЦен AND спрНом.isfolder = 0
|";
ЗапросSQL.УстановитьПараметр("ВыбДата", РабочаяДата());
ЗапросSQL.УстановитьПараметр("ВыбТипЦен", Константа.РозничныйТипЦен);
ТабРезультат = ЗапросSQL.ВыполнитьИнструкцию(ТекстЗапроса);
// Моментально получаем готовую таблицу значений с актуальными ценами
ТабРезультат.ВыбратьСтроки();
Пока ТабРезультат.ПолучитьСтроку() = 1 Цикл
Сообщить("Товар: " + ТабРезультат.Товар + " | Цена: " + ТабРезультат.РозничнаяЦена);
КонецЦикла;
КонецПроцедуры
Преимущества метода: Скорость работы возрастает в десятки и сотни раз. Построение прайс-листа на 50 000 позиций занимает менее одной секунды.
Недостатки: Требуется установка внешней компоненты 1С++ и базовые навыки написания SQL-запросов.
---
Если нам нужен чистый «срез последних» розничных цен, установленных на складах (даже если товара сейчас нет в наличии), но при этом мы не можем использовать внешние компоненты вроде 1С++, мы можем пойти альтернативным путем. Разберем логику этого метода:
Переоценка товаров в розницу, Поступление ТМЦ (в розницу) и Перемещение ТМЦ (на розничный склад).Этот способ позволяет сымитировать механизм работы виртуальной таблицы среза последних на чистом встроенном языке 1С 7.7. Именно это решение в итоге применил автор темы для получения корректного отчета.
---
Чтобы избежать расхождения данных в ваших отчетах и облегчить написание запросов, мы рекомендуем придерживаться следующих правил регламентного учета в «1С:Торговля и склад 7.7»:
Цены незамедлительно создавайте документ Переоценка товаров в розницу по всем розничным складам, где этот товар числится на остатках.ЦенаПрод ломает партионный учет и искажает финансовую прибыль. Регулярно проводите инвентаризацию и выравнивайте цены переоценкой.глПолучитьЦену() из глобального модуля конфигурации, так как в ней уже учтены все нюансы пересчета валют, курсов и единиц измерения.