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