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