В процессе разработки во встроенном языке 1С:Предприятие программисты часто сталкиваются с ошибкой «Обращение к процедуре объекта как к функции». Эта проблема особенно популярна при попытке написать лаконичный код «в одну строку» для свертки и загрузки табличных частей или таблиц значений. В этой статье мы подробно разберем, почему возникает данная ошибка, проанализируем сопутствующие логические изъяны в алгоритмах обработки таблиц и рассмотрим наиболее эффективные и безопасные способы решения этой задачи.
Давайте посмотрим на исходную строку кода, которая вызвала сбой на стороне пользователя:
Объект.Услуга.Загрузить(Объект.Услуга.Выгрузить().Свернуть("Наименование, Цена", "Количество, Сумма"));
При попытке выполнить эту конструкцию платформа выдает ошибку. Давайте разберем по шагам, что здесь происходит:
Объект.Услуга.Выгрузить() успешно отрабатывает и возвращает объект типа ТаблицаЗначений, содержащий копию данных табличной части.Свернуть("Наименование, Цена", "Количество, Сумма").Свернуть() во встроенном языке 1С является процедурой, а не функцией. Он преобразует исходную таблицу значений «на месте» (in-place) и не возвращает никакого значения (возвращает значение Неопределено).Загрузить() пытается обработать значение Неопределено вместо ожидаемого объекта типа ТаблицаЗначений, что и приводит к ошибке выполнения.Для исправления этой синтаксической ошибки необходимо разделить цепочку вызовов на отдельные шаги. Мы должны сначала выгрузить данные, затем выполнить процедуру свертки отдельной строкой и только после этого загрузить результат обратно в табличную часть:
// Правильное разделение операций
ТаблицаДанных = Объект.Услуга.Выгрузить();
ТаблицаДанных.Свернуть("Наименование, Цена", "Количество, Сумма");
Объект.Услуга.Загрузить(ТаблицаДанных);
Даже если мы полностью устраним синтаксическую ошибку, исходный алгоритм автора ветки останется абсолютно нерабочим. Рассмотрим подробнее, какие логические бомбы замедленного действия в нем скрыты.
Проанализируем фрагмент кода из первого сообщения темы:
Если Объект.Услуга.Количество() > 0 Тогда
Для каждого стр из Объект.Услуга Цикл
Если стр.Наименование = ВыбСтрока.Услуга Тогда
Стр.Количество = Стр.Количество + 1;
Стр.Сумма = Стр.Цена * Стр.Количество;
Иначе
НСТР = Объект.Услуга.Добавить();
НСТР.Наименование = ВыбСтрока.Услуга;
...
Объект.Услуга.Загрузить(Объект.Услуга.Выгрузить().Свернуть(...));
КонецЕсли;
КонецЦикла;
...
В коде используется цикл Для каждого стр из Объект.Услуга Цикл. Внутри этого цикла (в ветке Иначе) происходит добавление новой строки через метод Добавить() и перезапись всей коллекции через Загрузить(). В 1С категорически запрещено модифицировать состав коллекции во время ее обхода. Это приводит к непредсказуемому сдвигу индексов итератора: цикл может пропустить строки, пойти по бесконечному кругу или аварийно завершиться с системным исключением.
Конструкция проверки устроена так, что если текущая строка обхода не совпадает с искомой услугой, управление мгновенно переходит в ветку Иначе, где добавляется новая строка. Например, если в табличной части уже есть три разные услуги (А, Б и В), и мы пытаемся добавить услугу «Б»:
Иначе — создается новая строка «Б».В результате этот алгоритм будет добавлять новую позицию на каждом шаге несовпадения, увеличивая количество записей в геометрической прогрессии.
Помимо синтаксиса, важно помнить о фундаментальной особенности работы метода Свернуть(). При его вызове все колонки таблицы значений, которые не были явно указаны в параметрах группировки или суммирования, безвозвратно удаляются из таблицы.
Если ваша табличная часть содержит другие важные реквизиты (например, СтавкаНДС, ЕдиницаИзмерения, Спецификация или даже служебный реквизит НомерСтроки), то после выполнения Свернуть() и последующего Загрузить() эти колонки будут полностью очищены и примут значения по умолчанию. Это может привести к потере важных данных в документе.
Давайте рассмотрим правильные, производительные и безопасные способы добавления строк с группировкой, которые исключают использование ресурсоемких методов выгрузки и свертки всей таблицы в цикле.
Это самый оптимальный и лаконичный вариант. Вместо перебора всей таблицы в цикле мы выполняем точечный поиск строки по ключевым реквизитам. Если строка найдена — увеличиваем показатели, если нет — создаем новую.
// Шаг 1. Задаем параметры поиска по ключевым полям
ПараметрыОтбора = Новый Структура;
ПараметрыОтбора.Вставить("Наименование", ВыбСтрока.Услуга);
ПараметрыОтбора.Вставить("Цена", ВыбСтрока.Цена);
// Шаг 2. Ищем существующие строки в табличной части
НайденныеСтроки = Объект.Услуга.НайтиСтроки(ПараметрыОтбора);
Если НайденныеСтроки.Количество() > 0 Тогда
// Шаг 3. Если нашли — берем первую строку (на случай дублей) и обновляем показатели
ТекущаяСтрока = НайденныеСтроки[0];
ТекущаяСтрока.Количество = ТекущаяСтрока.Количество + 1;
ТекущаяСтрока.Сумма = ТекущаяСтрока.Цена * ТекущаяСтрока.Количество;
Иначе
// Шаг 4. Если совпадений нет — добавляем новую строку в табличную часть
НоваяСтрока = Объект.Услуга.Добавить();
НоваяСтрока.Наименование = ВыбСтрока.Услуга;
НоваяСтрока.Цена = ВыбСтрока.Цена;
НоваяСтрока.Количество = 1;
НоваяСтрока.Сумма = НоваяСтрока.Цена * НоваяСтрока.Количество;
КонецЕсли;
Если перед вами стоит задача загрузить большой массив новых данных (например, при парсинге файлов Excel или импорте из внешних систем), постоянный поиск строк на каждой итерации может замедлить работу системы. В этом случае эффективнее применить стратегию массового добавления с последующей финальной сверткой за рамками цикла.
Рассмотрим пример реализации этого подхода:
// 1. Быстро и безусловно добавляем все новые строки из внешнего источника в цикле
Для каждого ВыбСтрока Из МассивНовыхСтрок Цикл
НоваяСтрока = Объект.Услуга.Добавить();
НоваяСтрока.Наименование = ВыбСтрока.Услуга;
НоваяСтрока.Цена = ВыбСтрока.Цена;
НоваяСтрока.Количество = ВыбСтрока.Количество;
НоваяСтрока.Сумма = НоваяСтрока.Цена * НоваяСтрока.Количество;
КонецЦикла;
// 2. Выполняем свертку ТЧ ОДИН раз за пределами цикла
ТаблицаДанных = Объект.Услуга.Выгрузить();
// ВАЖНО: В группировку включаем абсолютно все реквизиты, данные которых нужно сохранить!
КолонкиГруппировки = "Наименование, Цена, СтавкаНДС, ЕдиницаИзмерения";
КолонкиСуммирования = "Количество, Сумма";
ТаблицаДанных.Свернуть(КолонкиГруппировки, КолонкиСуммирования);
// 3. Возвращаем свернутые данные обратно в табличную часть документа
Объект.Услуга.Загрузить(ТаблицаДанных);
Применение любого из предложенных вариантов не только навсегда избавит вас от ошибки «Обращение к процедуре как к функции», но и значительно повысит скорость работы вашей программы, защитив данные от случайной потери или перезаписи.