Как передать дату или реквизиты документа в функцию общего модуля в «1С:Предприятие»?

Программист 1С v8.3 (Обычные формы) IT и автоматизация бизнеса
← На главную

В процессе разработки в среде «1С:Предприятие» перед программистом часто возникает задача вынесения повторяющегося программного кода в общие модули. Это позволяет соблюдать принцип повторного использования кода и упрощает поддержку конфигурации. Однако при переносе логики из модуля формы или модуля объекта в общий модуль разработчики нередко сталкиваются со сложностями передачи контекста выполнения — например, даты документа, его номера или других реквизитов.

В этой статье мы подробно разберем, как правильно передавать параметры в функции общих модулей, разберем типичные ошибки синтаксиса, с которыми сталкиваются новички, и рассмотрим наиболее эффективные (с точки зрения производительности) способы решения подобных задач.

Постановка задачи и анализ исходной проблемы

Рассмотрим классическую ситуацию. У нас есть регистр сведений БазисныеПоказатели, в котором хранится информация о жирности номенклатуры. В коде существовала функция, которая возвращала базовый показатель жирности по конкретной номенклатуре:


Функция ПолучитьБазисныйЖир(Номенклатура) Экспорт
    Запрос = Новый Запрос;
    Запрос.Текст = 
        "ВЫБРАТЬ
        |	БазисныеПоказатели.Жирность
        |ИЗ
        |	РегистрСведений.БазисныеПоказатели КАК БазисныеПоказатели
        |ГДЕ
        |	БазисныеПоказатели.Номенклатура = &Номенклатура";
    Запрос.УстановитьПараметр("Номенклатура", Номенклатура);
    Выборка = Запрос.Выполнить().Выбрать();
    Пока Выборка.Следующий() Цикл
        Возврат Выборка.Жирность;
    КонецЦикла;
    Возврат 0;
КонецФункции

Бизнес-требования изменились: начиная с 1 января 2019 года, необходимо возвращать значение из другой колонки регистра — Жирность2. Для этого функции общего модуля потребовалось "знать" дату того документа, из которого она вызывается. Попытаемся разобраться, как правильно передать это значение и обработать его внутри функции.

Шаг 1. Передача даты документа через параметры функции

Самый простой и методологически верный способ передать любые данные в общий модуль — добавить новый параметр в сигнатуру функции. Давайте посмотрим, как это сделать правильно.

В общем модуле (например, с именем тфПроизводство) мы изменяем заголовок функции, добавляя параметр ДатаДокумента:

Функция ПолучитьБазисныйЖир(Номенклатура, ДатаДокумента) Экспорт

А при вызове этой функции из формы документа или модуля объекта мы передаем фактическое значение даты. Например, из модуля формы документа:


Жирность = тфПроизводство.ПолучитьБазисныйЖир(СтрокаТаблицы.Номенклатура, Объект.Date);

Обратите внимание: мы передаем в функцию конкретные значения примитивных типов или ссылок (в данном случае ссылку на номенклатуру и дату), а не весь тяжелый объект документа целиком. Передавать объект формы (реквизит Объект) или прикладной объект (ДокументОбъект) в общие модули без крайней необходимости не рекомендуется, так как это вызывает избыточное потребление памяти и замедляет работу системы (особенно при вызовах между клиентом и сервером).

Шаг 2. Разбор и устранение критических синтаксических ошибок

При объявлении функций с новыми параметрами разработчики часто совершают типичные синтаксические ошибки. Разберем одну из них, которая возникла в процессе обсуждения на форуме:

Ошибка компиляции: Ожидается символ ')' при объявлении функции следующего вида:

Функция ПолучитьБазисныйЖир(Номенклатура, СерияНоменклатуры = Неопределено, Объект.Дата) Экспорт

Проанализируем причины этой ошибки. Здесь допущены сразу две грубые ошибки синтаксиса языка 1С:

  1. Неверный порядок параметров. По правилам платформы «1С:Предприятие», параметры, имеющие значения по умолчанию (необязательные параметры, такие как СерияНоменклатуры = Неопределено), всегда должны располагаться в самом конце списка параметров функции. После параметра со значением по умолчанию нельзя указывать обязательные параметры без значений по умолчанию.
  2. Использование обращения через точку в объявлении функции. Параметры в объявлении функции — это локальные переменные, которые будут существовать только внутри этой функции. Они должны иметь простые имена (идентификаторы). Конструкции вида Объект.Дата допустимы только при вызове функции, но не при ее объявлении.

Исправленный вариант объявления функции:


Функция ПолучитьБазисныйЖир(Номенклатура, ДатаДокумента, СерияНоменклатуры = Неопределено) Экспорт

Шаг 3. Корректное сравнение дат в коде 1С

Еще одна распространенная ошибка связана со сравнением даты документа с граничной датой. На форуме предлагался следующий ошибочный вариант сравнения:

ДатаДокумента > Дата(2019, 1, 1)

Эта конструкция вызывает ошибку выполнения: Операции сравнения на больше-меньше допустимы только для значений совпадающих примитивных типов, поскольку функции Дата() с тремя числовыми параметрами в стандартном синтаксисе встроенного языка 1С не существует в таком виде, либо передаваемые типы интерпретируются системой неверно.

Для создания литерала типа Дата в 1С используется специальный синтаксис с одинарными кавычками:

'ГГГГММДД' или 'ГГГГММДДЧЧММСС'

Использование функции преобразования Дата(...) в данном случае избыточно, так как строка в одинарных кавычках уже является готовым литералом даты. Посмотрим на правильное условие сравнения:


Если ДатаДокумента > '20190101' Тогда
    // Логика для документов после 01.01.2019
Иначе
    // Логика для старых документов
КонецЕсли;

Шаг 4. Оптимизация запроса (перенос логики на сторону СУБД)

В решении, к которому пришел автор темы, выборка из базы данных всегда получает оба поля (Жирность и Жирность2), а затем условие по дате проверяется непосредственно в коде 1С. С точки зрения производительности СУБД и оптимизации трафика между сервером базы данных и сервером приложений, это не самый лучший вариант.

Гораздо эффективнее перенести проверку условия внутрь самого SQL-запроса, используя конструкцию ВЫБОР КОГДА ... ТОГДА ... ИНАЧЕ ... КОНЕЦ. Проанализируем оптимизированный вариант функции:


Функция ПолучитьБазисныйЖир(Номенклатура, ДатаДокумента) Экспорт
    
    Запрос = Новый Запрос;
    Запрос.Текст = 
        "ВЫБРАТЬ
        |	ВЫБОР
        |		КОГДА &ДатаДокумента > ДАТАВРЕМЯ(2019, 1, 1, 0, 0, 0) ТОГДА БазисныеПоказатели.Жирность2
        |		ИНАЧЕ БазисныеПоказатели.Жирность
        |	КОНЕЦ КАК Жирность
        |ИЗ
        |	РегистрСведений.БазисныеПоказатели КАК БазисныеПоказатели
        |ГДЕ
        |	БазисныеПоказатели.Номенклатура = &Номенклатура";
        
    Запрос.УстановитьПараметр("Номенклатура", Номенклатура);
    Запрос.УстановитьПараметр("ДатаДокумента", ДатаДокумента);
    
    Выборка = Запрос.Выполнить().Выбрать();
    Если Выборка.Следующий() Тогда
        Возврат Выборка.Жирность;
    КонецЕсли;
    
    Возврат 0;
    
КонецФункции

Этот вариант предпочтительнее по следующим причинам:

Шаг 5. Альтернативный быстрый метод получения данных из регистра

Если регистр сведений БазисныеПоказатели является непериодическим (то есть мы не храним историю изменения показателей во времени, а просто сопоставляем номенклатуру и жирность), то использование объекта Запрос для получения одной записи может быть избыточным.

У менеджера регистров сведений есть стандартный быстрый метод Получить(). Платформа 1С оптимизирует этот метод на уровне СУБД, и он выполняется быстрее, чем создание, компиляция и выполнение динамического запроса. Посмотрим, как переписать функцию с использованием этого метода:


Функция ПолучитьБазисныйЖир(Номенклатура, ДатаДокумента) Экспорт
    
    Отбор = Новый Структура("Номенклатура", Номенклатура);
    ДанныеРегистра = РегистрыСведений.БазисныеПоказатели.Получить(Отбор);
    
    // Если запись в регистре не найдена, вернется структура с пустыми значениями ресурсов
    Если ДанныеРегистра.Жирность = 0 И ДанныеРегистра.Жирность2 = 0 Тогда
        Возврат 0;
    КонецЕсли;
    
    Если ДатаДокумента > '20190101' Тогда
        Возврат ДанныеРегистра.Жирность2;
    Иначе
        Возврат ДанныеРегистра.Жирность;
    КонецЕсли;
    
КонецФункции

Предотвращение критической проблемы производительности (Запрос в цикле)

Рассматривая архитектуру этого решения, важно предупредить о самой опасной ошибке начинающих разработчиков — вызове запросов в цикле.

Если функция ПолучитьБазисныйЖир вызывается в цикле обхода строк табличной части документа (например, при изменении номенклатуры в каждой строке или при проведении документа), то система будет делать отдельный запрос к базе данных для каждой строки! Если в документе 100 строк, система выполнит 100 отдельных запросов к СУБД. Это классическая проблема производительности "N+1 запрос".

Как правильно решать эту задачу в промышленных масштабах:

Вместо того чтобы запрашивать данные для каждой номенклатуры отдельно, передавайте в общий модуль массив уникальной номенклатуры из табличной части документа. Функция выполнит всего один запрос к базе данных для всех позиций сразу и вернет соответствие, из которого вы быстро и без обращений к СУБД получите нужные значения.

Рассмотрим пример правильной пакетной обработки данных:


// Функция в общем модуле для пакетного получения показателей:
Функция ПолучитьБазисныйЖирДляСписка(МассивНоменклатуры, ДатаДокумента) Экспорт
    
    РезультатФункции = Новый Соответствие; // Ключ - Номенклатура, Значение - Жирность
    
    Запрос = Новый Запрос;
    Запрос.Текст = 
        "ВЫБРАТЬ
        |	БазисныеПоказатели.Номенклатура КАК Номенклатура,
        |	ВЫБОР
        |		КОГДА &ДатаДокумента > ДАТАВРЕМЯ(2019, 1, 1, 0, 0, 0) ТОГДА БазисныеПоказатели.Жирность2
        |		ИНАЧЕ БазисныеПоказатели.Жирность
        |	КОНЕЦ КАК Жирность
        |ИЗ
        |	РегистрСведений.БазисныеПоказатели КАК БазисныеПоказатели
        |ГДЕ
        |	БазисныеПоказатели.Номенклатура В (&МассивНоменклатуры)";
        
    Запрос.УстановитьПараметр("МассивНоменклатуры", МассивНоменклатуры);
    Запрос.УстановитьПараметр("ДатаДокумента", ДатаДокумента);
    
    Выборка = Запрос.Выполнить().Выбрать();
    Пока Выборка.Следующий() Цикл
        РезультатФункции.Вставить(Выборка.Номенклатура, Выборка.Жирность);
    КонецЦикла;
    
    Возврат РезультатФункции;
    
КонецФункции

А теперь посмотрим, как просто и эффективно вызвать этот код в модуле формы документа (например, при автозаполнении всей спецификации):


// Получаем массив уникальной номенклатуры из ТЧ документа:
МассивТоваров = Объект.Товары.ВыгрузитьКолонку("Номенклатура");
// Очистим массив от дублей:
ОбщегоНазначенияКлиентСервер.СвернутьМассив(МассивТоваров);

// Получаем все показатели одним запросом:
ТаблицаПоказателей = тфПроизводство.ПолучитьБазисныйЖирДляСписка(МассивТоваров, Объект.Дата);

// Заполняем показатели в строках документа без обращений к БД:
Для Каждого СтрокаТовара Из Объект.Товары Цикл
    БазовыйЖир = ТаблицаПоказателей[СтрокаТовара.Номенклатура];
    СтрокаТовара.Жирность = ?(БазовыйЖир = Неопределено, 0, БазовыйЖир);
КонецЦикла;

Использование пакетной обработки данных — это стандарт разработки «1С:Совместимо» и золотое правило высокопроизводительных систем на платформе 1С. Применяйте эти паттерны проектирования, чтобы ваши конфигурации работали быстро и надежно даже при больших объемах данных!

← На главную