При разработке и кастомизации решений на платформе 1С (включая конфигурации «1С:Розница 2.3», «1С:Управление торговлей 11», «1С:Комплексная автоматизация 2» и «1С:ERP») программисты часто сталкиваются с задачей получения цен из документа УстановкаЦенНоменклатуры. Казалось бы, на форме документа все наглядно: представлена удобная таблица или дерево, где для каждой позиции номенклатуры выведены старая и новая цены. Однако при попытке программно перебрать строки этой таблицы разработчик сталкивается со сложной проблемой: привычных колонок цен в табличной части нет, а вместо них на форме отображаются элементы со странными динамическими именами.
В этой статье мы подробно разберем внутреннюю архитектуру хранения цен в системе, выясним причину появления динамических колонок и рассмотрим практические методы получения необходимых данных с помощью запросов и программного кода.
Для начала проанализируем, почему визуальное представление документа на форме так сильно отличается от его физического хранения в базе данных (СУБД):
ДеревоЦен), в которой виды цен (закупочная, розничная, оптовая) расположены в виде отдельных горизонтальных колонок. Это сделано для удобства ручного ввода и визуального сравнения.ВидыЦен — простой плоский список видов цен, которые регистрируются текущим документом (содержит всего одну колонку ВидЦены).Товары — плоская таблица, в которой для каждого сочетания номенклатуры, характеристики и вида цены создается отдельная полноценная строка. Основными реквизитами здесь выступают Номенклатура, Характеристика, ВидЦены, Цена, Упаковка и Валюта.Выясним причину странного именования колонок на форме. При открытии документа платформа 1С динамически генерирует колонки таблицы на лету, исходя из списка выбранных видов цен. Имя каждой создаваемой колонки формируется по строгому правилу: к строковому префиксу добавляется уникальный идентификатор (UUID) конкретной записи из справочника ВидыЦен, из которого предварительно удалены дефисы.
Например, колонка для ввода новой цены будет иметь имя ВидЦеныb0019d2a832411e59e1b364b50b7ef2d, а колонка для отображения предыдущего значения — СтараяЦенаВидЦеныb0019d2a832411e59e1b364b50b7ef2d. Обращаться к таким колонкам напрямую в коде по статическому имени запрещено, так как в любой другой информационной базе у того же самого вида цены будет совершенно иной UUID, что мгновенно приведет к фатальной ошибке выполнения программы.
Важный нюанс: в самом документе старых цен нет! Документ установки цен фиксирует исключительно новые, вступающие в силу значения. Информация о старых ценах, которую пользователь видит на форме для сравнения или расчета процента наценки, запрашивается динамически непосредственно из базы данных в момент открытия формы. Источником этих данных служит регистр сведений ЦеныНоменклатуры.
---
Рассмотрим подробнее три основных сценария извлечения цен в зависимости от решаемой вами задачи.
Если документ уже записан в базу данных, то нет никакой необходимости обращаться к форме или пытаться обойти дерево на экране. Самый быстрый, производительный и правильный способ получить новые цены — сделать прямой запрос к табличной части Товары этого документа.
Проанализируем пример кода:
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ
| УстановкаТовары.Номенклатура КАК Номенклатура,
| УстановкаТовары.Характеристика КАК Характеристика,
| УстановкаТовары.ВидЦены КАК ВидЦены,
| УстановкаТовары.Цена КАК НоваяЦена
|ИЗ
| Документ.УстановкаЦенНоменклатуры.Товары КАК УстановкаТовары
|ГДЕ
| УстановкаТовары.Ссылка = &СсылкаДокумента";
Запрос.УстановитьПараметр("СсылкаДокумента", СсылкаНаДокумент);
РезультатЗапроса = Запрос.Выполнить();
Данный запрос вернет плоскую таблицу со всеми установленными документом ценами для каждой позиции номенклатуры и ее характеристики.
Для того чтобы узнать, какие именно цены действовали непосредственно перед проведением анализируемого документа, необходимо обратиться к виртуальной таблице СрезПоследних регистра сведений ЦеныНоменклатуры.
При этом критически важно правильно передать параметр времени в запрос. Если мы передадим простую дату документа, который уже проведен, то срез последних вернет нам новые цены, которые этот же документ и установил. Чтобы получить именно старые цены, мы должны использовать объект Граница с исключением момента времени документа.
Рассмотрим пример реализации:
// Формируем границу времени, исключая сам момент проведения текущего документа
ГраницаПериода = Новый Граница(СсылкаНаДокумент.МоментВремени(), ВидГраницы.Исключая);
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ
| ЦеныСрезПоследних.Номенклатура КАК Номенклатура,
| ЦеныСрезПоследних.Характеристика КАК Характеристика,
| ЦеныСрезПоследних.ВидЦены КАК ВидЦены,
| ЦеныСрезПоследних.Цена КАК СтараяЦена
|ИЗ
| РегистрСведений.ЦеныНоменклатуры.СрезПоследних(&Период, ВидЦены В (&МассивВидовЦен)) КАК ЦеныСрезПоследних";
// Извлекаем массив всех видов цен, задействованных в документе
МассивВидовЦен = СсылкаНаДокумент.ВидыЦен.ВыгрузитьКолонку("ВидЦены");
Запрос.УстановитьПараметр("Период", ГраницаПериода);
Запрос.УстановитьПараметр("МассивВидовЦен", МассивВидовЦен);
РезультатЗапроса = Запрос.Выполнить();
Этот способ является наиболее надежным и гарантирует получение корректных исторических данных непосредственно из регистра сведений.
Иногда при разработке интерактивных обработок, расширений форм или заполнении связанных реквизитов "на лету" (еще до записи документа в базу данных) требуется прочитать информацию из активного дерева цен на форме. В таком случае нам придется программно обойти элементы формы и сопоставить динамические UUID колонок с реальными ссылками на справочник ВидыЦен.
Рассмотрим, как это сделать по шагам:
// Обходим все колонки, добавленные в элемент формы ДеревоЦен
Для Каждого Колонка Из Элементы.ДеревоЦен.ПодчиненныеЭлементы Цикл
ИмяКолонки = Колонка.Имя;
// Отбираем только колонки цен, исключая служебные и колонки "старых" цен
Если Найти(ИмяКолонки, "ВидЦены") > 0 И Найти(ИмяКолонки, "СтараяЦена") = 0 Тогда
// Вырезаем текстовый префикс, оставляя только строковое представление UUID (без дефисов)
СтрокаUUID = Сред(ИмяКолонки, СтрДлина("ВидЦены") + 1);
// Для создания объекта УникальныйИдентификатор нам нужно вернуть дефисы в строку UUID
// Формат UUID: XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX
СтрокаUUIDСДефисами = Сред(СтрокаUUID, 1, 8) + "-"
+ Сред(СтрокаUUID, 9, 4) + "-"
+ Сред(СтрокаUUID, 13, 4) + "-"
+ Сред(СтрокаUUID, 17, 4) + "-"
+ Сред(СтрокаUUID, 21);
Попытка
Идентификатор = Новый УникальныйИдентификатор(СтрокаUUIDСДефисами);
СсылкаНаВидЦены = Справочники.ВидыЦен.ПолучитьСсылку(Идентификатор);
// Теперь мы можем обойти строки дерева цен на форме и получить значения по динамическому имени колонки
Для Каждого СтрокаДерева Из ДеревоЦен.ПолучитьЭлементы() Цикл
НоваяЦена = СтрокаДерева[ИмяКолонки];
// Формируем имя соответствующей колонки старой цены
ИмяКолонкиСтаройЦены = "СтараяЦена" + ИмяКолонки;
СтараяЦена = 0;
// Проверяем существование этой колонки в коллекции колонок дерева
Если ДеревоЦен.Колонки.Найти(ИмяКолонкиСтаройЦены) <> Неопределено Тогда
СтараяЦена = СтрокаДерева[ИмяКолонкиСтаройЦены];
КонецЕсли;
Номенклатура = СтрокаДерева.Номенклатура;
// Здесь мы можем выполнять любую необходимую нам логику обработки полученных цен
// ...
КонецЦикла;
Исключение
// Исключаем непредвиденные ошибки, если имя колонки не являлось валидным UUID
Продолжить;
КонецПопытки;
КонецЕсли;
КонецЦикла;
Этот алгоритм позволяет успешно взаимодействовать с формой в режиме реального времени и считывать цены непосредственно из элементов интерфейса управляемой формы.
В завершение обратим внимание на развитие архитектуры систем 1С. В последних версиях ERP-решений (УТ 11.5, КА 2.5, ERP 2.5, Розница 3.0) разработчики перевели платформу на новый движок ценообразования версии 2.5. Если в вашей базе включена эта опция, то цены регистрируются не в классическом регистре ЦеныНоменклатуры, а в обновленном регистре ЦеныНоменклатуры25.
Этот регистр поддерживает усложненную аналитику (например, разрез упаковок, серий или ценовых групп). При написании универсального программного кода рекомендуется использовать встроенный программный интерфейс (API) конфигурации — функции общих модулей ценообразования (например, ЦенообразованиеКлиентСервер), либо проверять константу применения ценообразования версии 2.5 перед выполнением прямых запросов к базе данных.