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