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