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

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

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

Рассмотрим, как устроен механизм заполнения табличной части оплат по документам поступления, и разберем варианты реализации двухэтапного подбора со списком-накопителем («корзиной»).

Анализ исходного механизма прямого выбора

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


Процедура КоманднаяПанельОплатаПоставщикамЗаполнитьПоПоступлениям(Кнопка)
    РеквизитыДокумента = ЭлементыФормы.ОплатаПоставщикам; 
    ФормаВыбора = Документы.ПоступлениеТоваровУслуг.ПолучитьФормуВыбора(, РеквизитыДокумента);
    ФормаВыбора.РежимВыбора = Истина; 
    ФормаВыбора.ЗакрыватьПриВыборе = Ложь; 
    ФормаВыбора.Открыть();
КонецПроцедуры

Процедура ОплатаПоставщикамОбработкаВыбора(Элемент, ВыбранноеЗначение, СтандартнаяОбработка)
    СтандартнаяОбработка = Ложь;
    ЗаполнитьОплатыПоОснованиюПТУУслуги(ВыбранноеЗначение);
КонецПроцедуры

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


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

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

Решение 1: Организация формы подбора с «корзиной»

Разберем по шагам построение классического механизма двухэтапного подбора через отдельную форму или обработку:

  1. Создание формы подбора: на форме размещаются два табличных поля. В верхней части отображается список документов ДокументСписок.ПоступлениеТоваровУслуг, в нижней части — таблица значений или СписокЗначений (корзина), содержащий отобранные документы.
  2. Перемещение в корзину: по двойному щелчку мыши или клавише Enter выбранная ссылка добавляется в нижний список, если её там еще нет. Пользователь может легко удалить ошибочные строки прямо из корзины.
  3. Перенос данных: по нажатию кнопки «Перенести в документ» форма подбора закрывается, а накопленный массив ссылок передается в вызывающую форму с помощью метода ОповеститьОВыборе().

Посмотрим на пример вызова формы подбора из формы документа:


Процедура КоманднаяПанельОплатаПоставщикамПодборПоступлений(Кнопка)
    ФормаПодбора = ПолучитьФорму("Обработка.ПодборДокументовПоступления.Форма", ЭтаФорма);
    ФормаПодбора.Открыть();
КонецПроцедуры

В самой форме подбора кнопка переноса формирует массив ссылок и оповещает владельца:


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

Решение 2: Пакетная оптимизация алгоритма заполнения

Когда в документ передается не одна ссылка, а целый Массив или СписокЗначений, алгоритм заполнения необходимо оптимизировать: выполнять один пакетный запрос вместо серии единичных обращений к базе данных.

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


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

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

Важные моменты реализации:

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