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