При переходе к разработке в стиле современных типовых конфигураций (таких как «Управление торговлей 11», «ERP Управление предприятием» или «Комплексная автоматизация») разработчики часто сталкиваются с трудностями переноса логики проведения из модуля объекта в модуль менеджера. Этот подход позволяет оптимизировать производительность, снизить время блокировок базы данных и сделать код более структурированным.
В этой статье мы подробно разберем типичные ошибки, возникающие при такой рефакторизации, выясним, почему система может выдавать ошибки компиляции типов, и напишем идеальный, оптимизированный код проведения «в стиле УТ».
Давайте проанализируем исходный код, предложенный автором темы, и выделим критические ошибки, которые мешают системе корректно провести документ.
1. Ошибка динамической типизации (почему модуль «ругается на тип»)
В сообщении №9 автор упоминает, что при попытке описать параметры в модуле менеджера система выдает ошибку компиляции. Это происходит из-за попытки жестко типизировать параметры в заголовке процедуры или функции. Например, синтаксическая конструкция вида:
Процедура ИнициализироватьТаблицыДляПроведенияОстатки(Ссылка КАК ДокументСсылка.ПриходТовара)
абсолютно недопустима в языке 1С:Предприятие. Платформа использует динамическую типизацию. Типы параметров не указываются при объявлении метода. Правильное объявление выглядит следующим образом:
Функция ИнициализироватьТаблицыДляПроведенияОстатки(Ссылка, ДополнительныеСвойства) Экспорт
2. Отсутствие директивы Экспорт
Для того чтобы метод, написанный в модуле менеджера, можно было вызвать из модуля объекта, он обязательно должен быть объявлен как публичный. Для этого в конце объявления метода необходимо добавить ключевое слово Экспорт. Без него компилятор выдаст ошибку: «Метод объекта не обнаружен».
3. Процедура вместо Функции
Поскольку логика проведения предполагает подготовку и расчет таблицы будущих движений в модуле менеджера с последующей передачей этих данных обратно в модуль объекта, метод инициализации таблиц обязательно должен быть Функцией, возвращающей результат (например, таблицу значений или массив таблиц), а не процедурой.
4. Ошибки синтаксиса вызова методов
В коде автора вызовы некоторых встроенных методов написаны без круглых скобок. Например, строки Запрос.ВыполнитьПакет и ЗаписанныйНаборЗаписей.Записать приведут к синтаксическим ошибкам. В 1С вызовы любых методов (даже не имеющих параметров) всегда должны заканчиваться круглыми скобками: Запрос.ВыполнитьПакет() и ЗаписанныйНаборЗаписей.Записать().
Давайте внимательно изучим запрос, предложенный автором, и разберем по шагам, какие проблемы производительности в нем скрыты:
ИЗ через запятую указаны табличная часть Документ.ПриходТовара.Товары и сам документ Документ.ПриходТовара. Связь между ними наложена в секции ГДЕ. Для СУБД это крайне неэффективно. На больших объемах данных такой запрос вызовет колоссальные задержки и блокировки.Ссылка. Если нам нужно получить реквизиты шапки (например, Склад), мы можем сделать это через обращение к реквизиту ссылки напрямую: ПриходТовараТовары.Ссылка.Склад. Это позволяет вообще исключить основную таблицу документа из запроса.&СсылкаТовары, но в коде модуля менеджера нет метода Запрос.УстановитьПараметр(). При выполнении кода система выдаст ошибку о неопределенном параметре.Ниже мы рассмотрим, как переписать этот запрос в соответствии со стандартами разработки фирмы «1С».
Давайте настроим правильное взаимодействие между модулем объекта и модулем менеджера.
В модуле объекта (в обработчике ОбработкаПроведения) мы подготавливаем структуру дополнительных свойств, вызываем функцию модуля менеджера для расчетов и загружаем полученную таблицу в движения регистра.
Рассмотрим код модуля объекта:
Процедура ОбработкаПроведения(Отказ, РежимПроведения)
// Устанавливаем свойства проведения
ЭтотОбъект.ДополнительныеСвойства.Вставить("ВидДвижения", ВидДвиженияНакопления.Приход);
// Вызываем функцию модуля менеджера для подготовки таблицы движений.
// Передаем ссылку на текущий документ и структуру дополнительных свойств.
ТаблицаОстатков = Документы.ПриходТовара.ИнициализироватьТаблицыДляПроведенияОстатки(ЭтотОбъект.Ссылка, ЭтотОбъект.ДополнительныеСвойства);
// Подготавливаем набор записей регистра остатков номенклатуры
ЗаписанныйНаборЗаписей = Движения.ОстаткиНоменклатуры;
ЗаписанныйНаборЗаписей.Записывать = Истина;
// Загружаем подготовленную таблицу в набор записей
ЗаписанныйНаборЗаписей.Загрузить(ТаблицаОстатков);
// В типовых конфигурациях принудительный вызов Записать() обычно не требуется,
// так как платформа запишет движения автоматически при завершении транзакции проведения.
// Но если вам требуется проанализировать движения в этой же транзакции позже, раскомментируйте строку ниже:
// ЗаписанныйНаборЗаписей.Записать();
КонецПроцедуры
В модуле менеджера создаем экспортную функцию. Мы оптимизируем запрос: избавимся от лишнего соединения таблиц, обратимся к складу через реквизит ссылки табличной части, установим параметры запроса и вернем результат пакета в виде таблицы значений.
Рассмотрим структуру модуля менеджера:
// Функция инициализирует данные для проведения по регистру остатков.
// Возвращает Таблицу Значений с рассчитанными движениями.
Функция ИнициализироватьТаблицыДляПроведенияОстатки(СсылкаДокумента, ДополнительныеСвойства) Экспорт
Запрос = Новый Запрос;
Запрос.МенеджерВременныхТаблиц = Новый МенеджерВременныхТаблиц;
// Обязательно устанавливаем параметры запроса!
Запрос.УстановитьПараметр("Ссылка", СсылкаДокумента);
// Текст запроса оптимизирован:
// 1. Убрано неэффективное соединение таблиц через запятую в секции ИЗ
// 2. Склад получается напрямую через точку от ссылки табличной части
Запрос.Текст =
"ВЫБРАТЬ
| ПриходТовараТовары.Ссылка.Склад КАК Склад,
| ПриходТовараТовары.Номенклатура КАК Номенклатура,
| СУММА(ПриходТовараТовары.Сумма) КАК Сумма,
| СУММА(ПриходТовараТовары.Количество * ПриходТовараТовары.ЕдиницаИзмерения.Коэффициент) КАК Количество
|ПОМЕСТИТЬ ВтТовары
|ИЗ
| Документ.ПриходТовара.Товары КАК ПриходТовараТовары
|ГДЕ
| ПриходТовараТовары.Ссылка = &Ссылка
|
|СГРУППИРОВАТЬ ПО
| ПриходТовараТовары.Ссылка.Склад,
| ПриходТовараТовары.Номенклатура
|
|ИНДЕКСИРОВАТЬ ПО
| Номенклатура
|;
|
|////////////////////////////////////////////////////////////////////////////////
|ВЫБРАТЬ
| ВтТовары.Склад КАК Склад,
| ВтТовары.Номенклатура КАК Номенклатура,
| ВтТовары.Сумма КАК Сумма,
| ВтТовары.Количество КАК Количество
|ИЗ
| ВтТовары КАК ВтТовары";
// Выполняем пакет запросов
РезультатПакета = Запрос.ВыполнитьПакет();
// Результат выполнения пакета возвращает массив.
// Первый запрос (индекс 0) помещает данные во временную таблицу ВтТовары.
// Второй запрос (индекс 1) выбирает данные из временной таблицы ВтТовары.
// Нам нужна именно вторая таблица пакета.
ТаблицаРезультата = РезультатПакета[1].Выгрузить();
Возврат ТаблицаРезультата;
КонецФункции
Если мы заглянем внутрь реальных документов конфигурации «Управление торговлей 11», то увидим еще более абстрактную и масштабируемую структуру. Давайте кратко разберем ее принципы, чтобы понимать вектор развития архитектуры 1С:
ДополнительныеСвойства.ТаблицыДляДвижений.Движения.ОстаткиНоменклатуры.Записать() заменяется на вызов универсального механизма проведения, который считывает подготовленные таблицы из дополнительных свойств и автоматически записывает их по всем регистрам одной транзакцией.Использование предложенного выше исправленного варианта кода позволит вам быстро и без ошибок перевести ваши документы на современную методику проведения, сделав программный код понятным, легко поддерживаемым и быстродействующим.