В процессе разработки расширений для типовых конфигураций (таких как 1С:Розница, 1С:Управление торговлей 11 или 1С:ERP) программисты часто сталкиваются с необходимостью кастомизации документа УстановкаЦенНоменклатуры. Одной из самых популярных задач является расчет средневзвешенной цены: когда новая цена должна рассчитываться исходя из остатков на складе и цен в документе поступления. Однако при попытке программно прочитать «Новую цену» прямо из формы документа возникает сложность: колонки с ценами в ДеревоЦен создаются динамически, и к ним невозможно обратиться по статичному имени.
В этой статье мы подробно разберем, как устроено ДеревоЦен, почему обычные методы обращения к реквизитам не работают и как правильно «вытащить» данные из динамических колонок для выполнения расчетов.
Рассмотрим ситуацию: нам необходимо реализовать расчет по формуле: (СтараяЦена * Остаток + НоваяЦена * Количество) / ОбщееКоличество. Проблема заключается в том, что в конфигураторе у объекта ДеревоЦен (тип ДанныеФормыДерево) есть только стандартные реквизиты: Номенклатура, Характеристика и т.д. Колонки для конкретных видов цен (например, «Розничная», «Оптовая») генерируются платформой программно в момент открытия формы или выбора видов цен пользователем.
Проанализируем структуру имен этих колонок. Если мы попробуем посмотреть их через отладчик, то увидим идентификаторы вида Цена_37f5b1... или Розничная1111. Эти имена не являются константами и могут меняться. Следовательно, код типа пСтрДеревоЦен.Цена вызовет ошибку «Поле объекта не обнаружено».
Для того чтобы получить доступ к данным, нам нужно понять, где система хранит карту соответствия между объектом ВидЦены и физическим именем колонки на форме. В типовых решениях 1С для этого используется вспомогательная коллекция ВыбранныеЦены. Это таблица на форме, которая содержит информацию о том, какие виды цен сейчас отображаются в дереве.
Обычно эта коллекция имеет следующие поля:
ВидыЦен.Рассмотрим метод, который позволит нам по конкретному виду цены узнать программное имя его колонки. Это критически важный шаг, без которого мы не сможем обратиться к данным в цикле.
Функция ПолучитьИмяКолонкиПоВидуЦен(ВидЦены)
// Ищем в коллекции ВыбранныеЦены строку с нужным видом цены
ПараметрыОтбора = Новый Структура("Ссылка", ВидЦены);
НайденныеСтроки = ЭтаФорма.ВыбранныеЦены.НайтиСтроки(ПараметрыОтбора);
Если НайденныеСтроки.Количество() > 0 Тогда
Возврат НайденныеСтроки[0].ИмяКолонки;
Иначе
Возврат "";
КонецЕсли;
КонецФункции
В данном примере мы используем метод НайтиСтроки для коллекции ВыбранныеЦены. Если вид цены найден, функция возвращает имя колонки, которое мы в дальнейшем используем как ключ доступа.
Теперь, когда у нас есть механизм определения имен колонок, разберем, как правильно организовать цикл по строкам ДеревоЦен и выполнить расчет. Важно помнить, что ДеревоЦен — это иерархическая структура, и нам нужно обрабатывать строки, содержащие номенклатуру.
Проанализируем следующий алгоритм:
Процедура РассчитатьСредневзвешенныеЦены()
// 1. Получаем нужный вид цены (например, Розничная)
ВидЦеныСсылка = Справочники.ВидыЦен.НайтиПоНаименованию("Розничная");
ИмяКолонкиНовойЦены = ПолучитьИмяКолонкиПоВидуЦен(ВидЦеныСсылка);
Если ПустаяСтрока(ИмяКолонкиНовойЦены) Тогда
Сообщить("Вид цены 'Розничная' не выбран в документе!");
Возврат;
КонецЕсли;
// 2. Получаем элементы дерева
ВсеСтрокиДерева = Объект.ДеревоЦен.ПолучитьЭлементы();
Для Каждого ТекущаяСтрока Из ВсеСтрокиДерева Цикл
// Если в дереве есть группировки, нужно обходить их рекурсивно
// или проверять наличие номенклатуры
Если ЗначениеЗаполнено(ТекущаяСтрока.Номенклатура) Тогда
// Получаем Новую цену, используя квадратные скобки []
// Это позволяет обратиться к свойству по его строковому имени
НоваяЦена = ТекущаяСтрока[ИмяКолонкиНовойЦены];
// Предположим, старая цена и остаток получены ранее запросом
СтараяЦена = ТекущаяСтрока.ЦенаСтарая; // Если реквизит добавлен вами
Остаток = ТекущаяСтрока.ОстатокНаСкладе;
КоличествоПоступление = ТекущаяСтрока.Количество;
Если (Остаток + КоличествоПоступление) > 0 Тогда
СредняяЦена = (СтараяЦена * Остаток + НоваяЦена * КоличествоПоступление) / (Остаток + КоличествоПоступление);
// Записываем результат в нужную колонку или вспомогательный реквизит
ТекущаяСтрока.РеквизитКорректор = СредняяЦена;
КонецЕсли;
КонецЕсли;
// Если есть подчиненные строки (характеристики), обходим их
ОбходПодчиненныхСтрок(ТекущаяСтрока, ИмяКолонкиНовойЦены);
КонецЦикла;
КонецПроцедуры
Рассмотрим несколько важных моментов, которые помогут избежать ошибок при реализации:
ТекущаяСтрока имеет тип ДанныеФормыЭлементДерева. Он ведет себя подобно структуре, что позволяет использовать синтаксис Строка["ИмяПоля"].Для Каждого. Если вам нужны данные об остатках или старых ценах из регистра, сначала выгрузите всё дерево в таблицу значений, соберите список всей номенклатуры, выполните ОДИН запрос к базе данных, а затем соедините результаты в памяти.Выясним, есть ли возможность упростить код. В модуле формы документа УстановкаЦенНоменклатуры часто присутствуют экспортные процедуры, такие как УстановкаЦенСервер.ПостроитьДеревоЦен. Если вы создаете расширение, вы можете «подсмотреть» в этих процедурах, как именно заполняется структура ПараметрыДанныхЦен. Иногда разработчики 1С уже хранят подготовленные соответствия видов цен и колонок в дополнительных свойствах объекта или в параметрах формы, что избавляет от необходимости ручного поиска в ВыбранныеЦены.
Подведем итог: Для доступа к динамическим ценам в 1С:Розница необходимо использовать коллекцию ЭтаФорма.ВыбранныеЦены для получения технического имени колонки, а затем обращаться к данным строки дерева через оператор []. Такой подход гарантирует стабильную работу кода даже при изменении состава отображаемых цен в документе.