Как правильно вынести проведение документа и запись в регистры в общий модуль?

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

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

Разберем, как грамотно организовать архитектуру проведения документа по аналогии с современными типовыми конфигурациями (ERP, Управление торговлей, КА), используя пакетные запросы, модуль менеджера и общий служебный модуль.

Архитектурная концепция разделения логики проведения

Вместо передачи огромных структур и построчного заполнения наборов записей в циклах применяется следующий паттерн:

  1. Модуль объекта документа инициализирует служебные структуры, передает управление в менеджер документа и общий модуль проведения, а также обрабатывает результат проверки остатков.
  2. Модуль менеджера документа формирует пакетные запросы для каждого регистра. Имена полей в выборке запроса точно соответствуют именам измерений и ресурсов регистра накопления.
  3. Общий модуль (например, ПроведениеСервер) выполняет пакетный запрос с использованием менеджера временных таблиц, считывает данные и заполняет наборы записей движений методом Движения[ИмяРегистра].Загрузить().

Шаг 1. Настройка обработчиков в модуле объекта документа

Проанализируем код модуля объекта. В обработчиках ПередЗаписью и ОбработкаПроведения мы подготавливаем структуру дополнительных свойств и вызываем методы формирования данных.


Процедура ПередЗаписью(Отказ, РежимЗаписи, РежимПроведения)
    
    Если ОбменДанными.Загрузка Тогда
        Возврат;
    КонецЕсли;
    
    ДополнительныеСвойства.Вставить("ЭтоНовый", ЭтоНовый());
    ДополнительныеСвойства.Вставить("РежимЗаписи", РежимЗаписи);
    
КонецПроцедуры

Процедура ОбработкаПроведения(Отказ, РежимПроведения)
    
    // 1. Инициализируем структуру дополнительных свойств для проведения
    ПроведениеСервер.ИнициализироватьДополнительныеСвойстваДляПроведения(Ссылка, ДополнительныеСвойства, РежимПроведения);
    ДополнительныеСвойства.Вставить("МоментВремени", МоментВремени());
    
    // 2. Формируем таблицы движений через модуль менеджера
    Документы.ИмяТвоегоДокумента.ИнициализироватьДанныеДокумента(Ссылка, ДополнительныеСвойства);
    
    // 3. Выполняем проверку отрицательных остатков, если они рассчитывались в запросе
    Если ПроверитьОтрицательныеОстатки(ДополнительныеСвойства.ДляПроведения.СтруктураВременныеТаблицы.МенеджерВременныхТаблиц) Тогда
        Отказ = Истина;
        Возврат;
    КонецЕсли;
    
    // 4. Подготавливаем флаги записи наборов движений
    ПроведениеСервер.ПодготовитьНаборыЗаписейКРегистрацииДвижений(ЭтотОбъект);
    
    // 5. Заполняем движения сформированными таблицами
    ПроведениеСервер.СформироватьДвиженияДокумента(ДополнительныеСвойства, Движения);
    
КонецПроцедуры

Шаг 2. Формирование запросов движений в модуле менеджера

Рассмотрим модуль менеджера документа. Здесь создаются тексты запросов, которые формируют временные таблицы и итоговые таблицы движений для каждого регистра.


Процедура ИнициализироватьДанныеДокумента(ДокументСсылка, ДополнительныеСвойства, Регистры = Неопределено) Экспорт
    
    Запрос = Новый Запрос;
    Запрос.МенеджерВременныхТаблиц = ДополнительныеСвойства.ДляПроведения.СтруктураВременныеТаблицы.МенеджерВременныхТаблиц;
    
    ЗаполнитьПараметрыИнициализации(Запрос, ДокументСсылка);
    
    // Передаем параметры проведения в запрос
    Для Каждого КлючЗначение Из Новый Структура("МоментВремени,ЭтоНовый,РежимЗаписи,РежимПроведения") Цикл
        Значение = Неопределено;
        Если ДополнительныеСвойства.Свойство(КлючЗначение.Ключ, Значение) Тогда
            Запрос.Параметры.Вставить(КлючЗначение.Ключ, Значение);
        КонецЕсли;
    КонецЦикла;
    
    ТекстыЗапроса = Новый СписокЗначений;
    
    // Добавляем тексты запросов для нужных регистров
    ТекстЗапросаТаблицаОстаткиНаСкладах(Запрос, ТекстыЗапроса, Регистры);
    
    ПроведениеСервер.ИнициализироватьТаблицыДляДвижений(Запрос, ТекстыЗапроса, ДополнительныеСвойства.ТаблицыДляДвижений, Истина);
    
КонецПроцедуры

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

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

Шаг 3. Контроль остатков при проведении

Проанализируем задачу контроля остатков при оперативном списании. Проверку можно реализовать несколькими способами:

  1. Через промежуточную временную таблицу в общем пакете запросов. Текст запроса расчета нехватки помещает данные в таблицу ОтрицательныеОстатки без указания второго параметра в ТекстыЗапроса.Добавить(Текст). Затем из модуля объекта выполняется выборка из этой временной таблицы.
  2. Контроль после проведения (новая методика). Движения записываются в базу данных методом Движения.ОстаткиНаСкладах.Записать(), после чего вторым запросом к виртуальной таблице РегистрыНакопления.ОстаткиНаСкладах.Остатки проверяется наличие отрицательных значений (КоличествоОстаток < 0). При обнаружении нехватки устанавливается Отказ = Истина, и транзакция отменяется.

Посмотрим на пример функции проверки остатков через временную таблицу:


Функция ПроверитьОтрицательныеОстатки(МенеджерВременныхТаблиц)
    
    Запрос = Новый Запрос;
    Запрос.МенеджерВременныхТаблиц = МенеджерВременныхТаблиц;
    Запрос.Текст = 
    "ВЫБРАТЬ
    |   ВТ.Номенклатура КАК Номенклатура,
    |   ВТ.КоличествоОстаток КАК КоличествоОстаток,
    |   ВТ.КоличествоПоДокументу КАК КоличествоПоДокументу
    |ИЗ
    |   ОтрицательныеОстатки КАК ВТ";
    
    Результат = Запрос.Выполнить();
    Если Результат.Пустой() Тогда
        Возврат Ложь;
    КонецЕсли;
    
    Выборка = Результат.Выбрать();
    Пока Выборка.Следующий() Цикл
        Сообщение = Новый СообщениеПользователю;
        Сообщение.Текст = СтрШаблон("Недостаточно товара %1: требуется %2, на остатке %3", 
            Выборка.Номенклатура, Выборка.КоличествоПоДокументу, Выборка.КоличествоОстаток);
        Сообщение.Сообщить();
    КонецЦикла;
    
    Возврат Истина;
    
КонецФункции

Преимущества описанного подхода

  1. Высокая скорость проведения: СУБД выполняет единый оптимизированный пакетный запрос вместо сотен мелких операций в цикле платформы 1С.
  2. Чистота кода: Модуль объекта содержит только общую канву алгоритма, а логика выборки данных по каждому разделу учета изолирована в модуле менеджера.
  3. Стандартизация: Подход полностью соответствует методическим рекомендациям фирмы 1С и архитектуре типовых решений на базе БСП.
← На главную