В процессе разработки на платформе 1С:Предприятие начинающие программисты часто сталкиваются с ситуацией, когда код, который логически кажется верным (или работает в Консоли запросов), выдает ошибку при исполнении. Одной из самых распространенных является ошибка «Переменная не определена». Рассмотрим эту проблему подробно на примере работы со свойствами контрагентов (механизм дополнительных свойств в старых конфигурациях, таких как УТ 10.3, УПП 1.3 или самописных решениях).
Разберем ситуацию: нам необходимо отобрать контрагентов, у которых установлено определенное свойство (например, статус «VIP»). В Консоли запросов мы просто выбираем нужное значение параметра мышкой из списка. Однако при переносе текста запроса в модуль, мы пишем следующий код:
Запрос.Текст = "... ГДЕ ЗначенияСвойствОбъектов.Свойство = &Свойство";
Запрос.УстановитьПараметр("Свойство", VIP); // Ошибка здесь
Система выдает ошибку: «Переменная не определена (VIP)». Почему это происходит? Дело в том, что для компилятора VIP — это просто набор символов. Программа не знает, что вы подразумеваете конкретный элемент плана видов характеристик или справочника. Переменная VIP нигде не объявлена и ей не присвоено значение (ссылка на объект базы данных).
Чтобы передать в запрос конкретное свойство, нам нужно сначала найти ссылку на него в базе данных. В старых типовых конфигурациях свойства объектов обычно хранятся в Плане Видов Характеристик (ПВХ) СвойстваОбъектов.
Рассмотрим несколько способов получения этой ссылки, от простых (но ненадежных) к правильным.
В обсуждении было предложено использовать методы поиска. Это самый быстрый способ заставить код работать, но он несет риски: если пользователь переименует свойство или при обмене данными изменится код, ваша обработка перестанет работать.
// Поиск по наименованию
СвойствоVIP = ПланыВидовХарактеристик.СвойстваОбъектов.НайтиПоНаименованию("VIP");
// ИЛИ поиск по коду (как сделал автор темы)
СвойствоVIP = ПланыВидовХарактеристик.СвойстваОбъектов.НайтиПоКоду("06828");
Запрос.УстановитьПараметр("Свойство", СвойствоVIP);
Если свойство «VIP» является критически важным для логики работы программы, правильнее всего создать его как Предопределенный элемент в конфигураторе. Тогда к нему можно обращаться напрямую через имя, и никакой поиск не потребуется.
// Обращение к предопределенному элементу
Запрос.УстановитьПараметр("Свойство", ПланыВидовХарактеристик.СвойстваОбъектов.VIP);
В теме форума мы увидели классическую ошибку новичка: получение списка контрагентов, а затем вызов функции ЗначениеСвойстваVIPКонтрагента() внутри цикла обхода этого списка. Внутри функции создается и выполняется запрос.
Важно: Никогда не выполняйте запросы внутри цикла! Это многократно увеличивает нагрузку на базу данных (Проблема N+1 запросов).
Вместо этого мы должны получить все необходимые данные одним запросом, используя Левое соединение. Давайте объединим таблицу контрагентов с регистром сведений ЗначенияСвойствОбъектов.
Посмотрим на правильный и оптимизированный пример кода:
Функция ПолучитьДанныеПоКонтрагентам()
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ
| Контрагенты.Ссылка КАК Контрагент,
| ЕСТЬNULL(ЗначенияСвойств.Значение, ЛОЖЬ) КАК ЭтоVIP
|ИЗ
| Справочник.Контрагенты КАК Контрагенты
| ЛЕВОЕ СОЕДИНЕНИЕ РегистрСведений.ЗначенияСвойствОбъектов КАК ЗначенияСвойств
| ПО Контрагенты.Ссылка = ЗначенияСвойств.Объект
| И (ЗначенияСвойств.Свойство = &СвойствоVIP)";
// Получаем ссылку на свойство один раз перед запросом
СвойствоLink = ПланыВидовХарактеристик.СвойстваОбъектов.НайтиПоНаименованию("VIP");
Запрос.УстановитьПараметр("СвойствоVIP", СвойствоLink);
Возврат Запрос.Выполнить().Выгрузить();
КонецФункции
Теперь в результате запроса у нас будет колонка ЭтоVIP, и нам не нужно делать дополнительные проверки в цикле.
Еще один важный момент, затронутый в обсуждении, — расчет сроков оплаты. Автор использовал прибавление секунд:
ПредельнаяДатаОплаты = ДатаОплаты + 15552000; // 180 дней
Это плохая практика, так как:
В 1С есть встроенные механизмы для работы с датами. Рассмотрим более корректные варианты:
Вариант 1. Использование функции ДобавитьМесяц
Это самый надежный способ для календарных расчетов.
// Добавляем ровно 6 месяцев
ПредельнаяДатаОплаты = ДобавитьМесяц(ДатаОплаты, 6);
Вариант 2. Использование констант времени
Если нужно добавить именно дни, лучше использовать явное умножение, чтобы код был понятен.
СуткиВСекундах = 86400; // 60 * 60 * 24
ПредельнаяДатаОплаты = ДатаОплаты + (180 * СуткиВСекундах);
Чтобы избежать подобных ошибок в будущем, следуйте простым правилам:
Запрос.УстановитьПараметр, должны быть определены в коде до момента вызова метода.НайтиПоНаименованию, но будьте готовы к тому, что имя могут изменить.Пример правильного перебора выборки:
РезультатЗапроса = Запрос.Выполнить();
// Создаем новую переменную для выборки, а не перезаписываем РезультатЗапроса
Выборка = РезультатЗапроса.Выбрать();
Пока Выборка.Следующий() Цикл
// Обработка данных
КонецЦикла;