При разработке и кастомизации конфигураций на платформе «1С:Предприятие 8» разработчики часто сталкиваются со странными ошибками компиляции и отладки. Одной из таких неочевидных проблем является ошибка «{(1,1)}: Ожидается выражение». Чаще всего она возникает при попытке провести документ (например, Расходный кассовый ордер) или в процессе анализа его кода в отладчике.
В этой статье мы подробно разберем, почему возникает эта ошибка, какие критические синтаксические и логические неточности были допущены в исходном коде, и как правильно переписать алгоритм проверки остатков денежных средств, чтобы он работал быстро и безошибочно.
Для начала проанализируем поведение платформы во время отладки. Почему в окне вычисления выражений или в табло мы видим эту ошибку? Причина кроется в непонимании принципов работы механизма вычисления выражений 1С:
ЭтотОбъект.СуммаДокумента или Результат.Количество() > 0). Если мы выделим строку целиком, захватив управляющие конструкции встроенного языка (такие как Если, Тогда, Иначе, КонецЕсли), и нажмем Shift + F9, система выдаст ошибку {(1,1)}: Ожидается выражение. Платформа не может исполнить блок ветвления в окне оценки выражения.(1,1).Решение: Для проверки условий в отладчике необходимо выделять только то, что находится между ключевыми словами. Вместо проверки всей строки Если Результат.Количество() > 0 Тогда, нужно вычислять только выражение Результат.Количество() > 0.
Теперь рассмотрим исходный код, представленный в вопросе. В нем содержатся грубые синтаксические ошибки, которые гарантированно приведут к аварийному завершению работы программы при попытке проведения документа.
Первая критическая ошибка кроется в строке:
Результат = Запрос.Выполнить.Выгрузить;
Во встроенном языке 1С методы Выполнить() (объекта Запрос) и Выгрузить() (объекта РезультатЗапроса) являются функциями. Вызов любых функций и методов в 1С обязательно должен сопровождаться круглыми скобками, даже если у них нет параметров. Если скобки опущены, компилятор воспринимает эти вызовы как обращение к свойствам объектов, которых не существует. Это вызывает ошибку выполнения.
Вторая синтаксическая ошибка находится в строке установки параметра даты:
Запрос.УстановитьПараметр("НаДату", ?(Дата = НачалоДня(ТекущаяДата), ТекущаяДата, Дата));
Здесь системная функция ТекущаяДата() вызвана без круглых скобок. Компьютер воспримет ТекущаяДата как неопределенную переменную, что приведет к ошибке компиляции модуля.
Даже если мы исправим синтаксис встроенного языка, сам запрос написан крайне неоптимально и содержит логические изъяны. Проанализируем их подробнее.
На форумах часто предлагают заменить строку выбора суммы на СУММА(ЕстьNULL(ДенежныеСредстваОстатки.СуммаОборот, 0)). Рассмотрим, почему это методологически неверно. Если по заданным фильтрам (касса, тип денежных средств) в базе вообще не окажется записей, то запрос вернет пустую строку, а групповая функция СУММА вернет значение NULL для всего поля, несмотря на то, что внутри нее проверялись значения. Чтобы гарантированно защитить код от получения значения NULL, функцию проверки нужно ставить поверх агрегатной функции:
ЕстьNULL(СУММА(ДенежныеСредстваОстатки.СуммаОборот), 0) КАК СуммаОстаток
Для проверки текущего остатка перед списанием в коде используется виртуальная таблица РегистрНакопления.ДенежныеСредства.ОстаткиИОбороты с детализацией до регистратора. Это грубейшая ошибка проектирования. Обращение к таблице остатков и оборотов с периодичностью «Регистратор» заставляет СУБД выполнять сложнейшие соединения физических таблиц итогов и движений. Для проверки остатков всегда нужно использовать легкую виртуальную таблицу Остатки.
Условие ДенежныеСредстваОстатки.Регистратор <> &Регистратор в секции ГДЕ заставляет СУБД сначала выбрать абсолютно все записи из базы данных, а уже затем отфильтровать их по условию. Это приводит к колоссальным нагрузкам на сервер. Для того чтобы при перепроведении документа исключить его собственные движения из остатка, в 1С применяется механизм Границы периода с исключением момента времени документа.
Давайте объединим все исправления и перепишем этот алгоритм в соответствии со стандартами разработки «1С:Предприятие». Мы заменим виртуальную таблицу на Остатки, применим объект Граница для корректного исключения движений текущего документа и исправим все синтаксические ошибки встроенного языка.
Рассмотрим по шагам правильную реализацию проверки лимита кассы:
// Инициализируем объект запроса
Запрос = Новый Запрос;
// Используем производительную таблицу Остатки вместо ОстаткиИОбороты.
// Фильтры по измерениям переносим внутрь параметров виртуальной таблицы.
Запрос.Текст =
"ВЫБРАТЬ РАЗРЕШЕННЫЕ
| ЕстьNULL(ДенежныеСредстваОстатки.СуммаОстаток, 0) КАК СуммаОстаток
|ИЗ
| РегистрНакопления.ДенежныеСредства.Остатки(&НаДату,
| СтруктурнаяЕдиница = &СтруктурнаяЕдиница
| И ТипДенежныхСредств = &ТипДенежныхСредств
| И БанковскийСчетКасса = &Касса) КАК ДенежныеСредстваОстатки";
// Установка параметров
Запрос.УстановитьПараметр("СтруктурнаяЕдиница", ЭтотОбъект.СтруктурнаяЕдиница);
Запрос.УстановитьПараметр("ТипДенежныхСредств", Перечисления.ТипыДенежныхСредств.Наличные);
Запрос.УстановитьПараметр("Касса", Касса);
// Определяем момент времени контроля остатков.
// Если документ уже записан в базу, используем Границу "Исключая" его собственный МоментВремени.
// Если это новый документ, проверяем остатки на указанную дату документа.
Если ЗначениеЗаполнено(Ссылка) Тогда
ГраницаКонтроля = Новый Граница(МоментВремени(), ВидГраницы.Исключая);
Иначе
ГраницаКонтроля = ?(Дата = НачалоДня(ТекущаяДата()), ТекущаяДата(), Дата);
КонецЕсли;
Запрос.УстановитьПараметр("НаДату", ГраницаКонтроля);
// Выполняем запрос с обязательным использованием круглых скобок для вызова методов
РезультатЗапроса = Запрос.Выполнить();
ТаблицаОстатков = РезультатЗапроса.Выгрузить();
// Проверяем результат выполнения запроса
Если ТаблицаОстатков.Количество() = 0 ИЛИ ТаблицаОстатков[0].СуммаОстаток < ЭтотОбъект.СуммаДокумента Тогда
Отказ = Истина;
// Рассчитываем текущий баланс для вывода сообщения пользователю
ТекущийБаланс = ?(ТаблицаОстатков.Количество() = 0, 0, ТаблицаОстатков[0].СуммаОстаток);
Сообщение = Новый СообщениеПользователю;
Сообщение.Текст = "Недостаточно денежных средств для проведения операции! Текущий остаток кассы: " + Строка(ТекущийБаланс);
Сообщение.Сообщить();
КонецЕсли;
Проанализируем преимущества обновленного кода:
Остатки вместо фильтрации в ГДЕ позволяет СУБД моментально выдать результат, используя заранее подготовленные индексы таблиц итогов 1С.Граница гарантирует, что при повторном проведении или изменении расходного ордера система не будет учитывать его собственные, уже существующие в регистре движения. Это исключает ложные срабатывания контроля остатков.NULL вынесена за пределы агрегатных функций, что гарантирует стабильную работу при нулевых остатках в кассе.