Как правильно заполнить табличную часть "ПлатежноРасчетныеДокументы" в документе "Счет-фактура выданный" с помощью расширения в 1С:Бухгалтерии 3.0?

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

При работе с конфигурациями 1С:Предприятия 8.3, особенно с типовыми решениями вроде 1С:Бухгалтерии 3.0 (БП 3.0), часто возникает необходимость дорабатывать функциональность документов без изменения основной конфигурации. Расширения предоставляют мощный инструмент для этих целей. Однако, при модификации форм документов, в частности при заполнении их табличных частей программно, мы сталкиваемся с особенностями клиент-серверного взаимодействия и правилами работы с данными формы.

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

Анализ исходной проблемы и ошибок

Наш коллега столкнулся с проблемой при попытке заполнить табличную часть "ПлатежноРасчетныеДокументы" в документе "Счет-фактура выданный" через расширение. Он добавил кнопку на форму и написал для нее обработчик следующего вида:


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

На первый взгляд, код выглядит логично: мы выполняем запрос на сервере, получаем данные, а затем пытаемся добавить их в табличную часть. Однако, такая реализация не будет работать корректно, и вот почему:

  1. Контекст выполнения процедуры: Процедура помечена как &НаСервере. Это означает, что весь код внутри нее выполняется на сервере. Когда мы работаем с управляемыми формами в 1С:Предприятии, данные формы (то, что видит пользователь на экране) существуют на клиенте. Серверный код не имеет прямого доступа к элементам формы, а значит, и к ее табличным частям в том виде, в каком они представлены на экране.
  2. Переменная Объект в серверном контексте: В процедуре, выполняемой на сервере, переменная Объект ссылается на объект базы данных (в данном случае, документ "Счет-фактура выданный" как объект метаданных), а не на данные формы, которые отображаются пользователю. Соответственно, модификация Объект.ПлатежноРасчетныеДокументы.Добавить() на сервере не приводит к изменению табличной части формы на клиенте. Эти изменения относятся к данным, которые будут записаны в базу данных при сохранении, но не к текущему представлению формы.
  3. Возвращаемый тип `Добавить()`: Метод Добавить() для коллекций данных формы, когда он вызывается в клиентском контексте, возвращает объект типа ДанныеФормыЭлементКоллекции. Именно с этим объектом мы должны работать для заполнения новой строки на форме. В серверном контексте работа с коллекциями объекта базы данных происходит иначе, но главное - эти изменения не синхронизируются автоматически с формой.

В данном случае, запрос выполнялся на сервере, но результаты запроса так и не попадали на клиентскую форму для ее отображения. Отладчик не отрабатывал точку остановки, потому что логика работы с данными формы была нарушена.

Пошаговое решение проблемы заполнения табличной части через расширение

Для успешного решения задачи нам потребуется корректно организовать клиент-серверное взаимодействие и убедиться, что все необходимые элементы формы доступны для модификации в расширении. Разберем шаги, которые мы предпримем для создания работающего решения.

  1. Шаг 1: Добавление элементов формы в расширение

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

    • Форма документа "Счет-фактура выданный".
    • Табличная часть "ПлатежноРасчетныеДокументы".
    • Реквизиты табличной части, которые мы собираемся заполнять: ДатаДокумента, НомерДокумента.

    Как это сделать:

    Откройте конфигуратор, перейдите к вашему расширению. Найдите документ "Счет-фактура выданный", затем его основную форму. Добавьте эту форму в расширение (например, через контекстное меню "Добавить в расширение"). После добавления формы, в дереве элементов формы вы увидите табличную часть "ПлатежноРасчетныеДокументы". Также добавьте ее в расширение. Затем, внутри этой табличной части, убедитесь, что реквизиты ПлатежноРасчетныеДокументыДатаДокумента и ПлатежноРасчетныеДокументыНомерДокумента также присутствуют в расширении. Это обеспечит, что расширение имеет полное право на модификацию этих элементов формы.

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

    Как мы уже выяснили, выполнение запросов к базе данных должно происходить на сервере. Поэтому мы создадим отдельную серверную функцию, которая будет получать необходимые данные об оплатах по договору и возвращать их на клиент. Эта функция будет располагаться в модуле формы документа, добавленной в расширение, или в общем модуле расширения с соответствующим атрибутом &НаСервере.

    Рассмотрим пример такой функции:

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

    В этой функции мы:

    • Определяем параметры запроса, используя данные, переданные с клиента (дату документа и договор контрагента). Мы можем также добавить параметры для организации и контрагента, чтобы выборка была более точной, используя Объект.Организация и Объект.Контрагент.
    • Формируем текст запроса для получения номера и даты входящего документа из регистра ПоступлениеНаРасчетныйСчет.
    • Выполняем запрос и с помощью метода Выгрузить() получаем результат в виде ТаблицыЗначений. Эту таблицу мы возвращаем на клиент.
  3. Шаг 3: Реализация клиентской процедуры для заполнения табличной части формы

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

    Рассмотрим скорректированный код процедуры, который должен быть размещен в модуле формы документа с атрибутом &НаКлиенте:

    
    &НаКлиенте
    Процедура СФВ_ЗаполнитьОплаты(Команда)
        // Перед заполнением очистим существующую табличную часть, если это необходимо
        // В данном примере мы просто добавляем новые строки. 
        // Если нужно обновить или очистить, потребуется дополнительная логика.
        // Например: Объект.ПлатежноРасчетныеДокументы.Очистить();
    
        // Вызываем серверную функцию для получения данных
        // Передаем параметры из данных формы документа
        ТабОплаты = ПолучитьОплатыПоДоговоруНаСервере(Объект.Дата, Объект.ДоговорКонтрагента);
        
        Если ТабОплаты.Количество() > 0 Тогда
            // Итерируем по каждой строке полученной ТаблицыЗначений
            Для Каждого ТекСтрокаОплаты Из ТабОплаты Цикл
                // Добавляем новую строку в табличную часть формы на клиенте
                СтрокаТЧ = Объект.ПлатежноРасчетныеДокументы.Добавить();
                
                // Заполняем реквизиты новой строки данными из серверной выборки
                СтрокаТЧ.ПлатежноРасчетныеДокументыДатаДокумента = ТекСтрокаОплаты.ДатаВходящегоДокумента;
                СтрокаТЧ.ПлатежноРасчетныеДокументыНомерДокумента = ТекСтрокаОплаты.НомерВходящегоДокумента;
                
                // Если требуется заполнить другие реквизиты (например, Сумма), 
                // их также нужно добавить в запрос и передать в строку ТЧ.
                // СтрокаТЧ.Сумма = ТекСтрокаОплаты.Сумма;
                
            КонецЦикла;    
        Иначе
            Сообщить("Оплат по данному договору - " + СокрЛП(Объект.ДоговорКонтрагента) + " не было."); 
        КонецЕсли;
    КонецПроцедуры
    

    Давайте подробно разберем, что происходит в этой процедуре:

    • `&НаКлиенте`: Это ключевой атрибут, указывающий, что код выполняется на стороне клиента, где и находится форма документа со всеми ее элементами.
    • ТабОплаты = ПолучитьОплатыПоДоговоруНаСервере(Объект.Дата, Объект.ДоговорКонтрагента);: Здесь мы вызываем нашу серверную функцию ПолучитьОплатыПоДоговоруНаСервере. Важно, что в клиентской процедуре мы передаем параметры Объект.Дата и Объект.ДоговорКонтрагента, которые являются данными формы документа. Эти данные будут переданы на сервер для выполнения запроса. Результат (ТаблицаЗначений) возвращается и сохраняется в клиентской переменной ТабОплаты.
    • Если ТабОплаты.Количество() > 0 Тогда: Мы проверяем, были ли получены какие-либо оплаты.
    • Для Каждого ТекСтрокаОплаты Из ТабОплаты Цикл: Мы итерируем по каждой строке полученной с сервера ТаблицыЗначений.
    • СтрокаТЧ = Объект.ПлатежноРасчетныеДокументы.Добавить();: Это самый важный шаг. В клиентском контексте Объект ссылается на данные формы документа. Метод Добавить(), вызванный у коллекции табличной части формы (Объект.ПлатежноРасчетныеДокументы), создает новую строку в табличной части формы и возвращает ссылку на эту новую строку в виде объекта ДанныеФормыЭлементКоллекции.
    • СтрокаТЧ.ПлатежноРасчетныеДокументыДатаДокумента = ТекСтрокаОплаты.ДатаВходящегоДокумента;: Мы заполняем реквизиты новой строки табличной части формы данными, полученными из серверной выборки. Обратите внимание на полное имя реквизита: ПлатежноРасчетныеДокументыДатаДокумента (состоит из имени табличной части и имени реквизита внутри нее).
    • Сообщить(...): Предоставляем обратную связь пользователю, если оплат не найдено.

    Таким образом, мы видим, как данные сначала извлекаются с сервера, а затем используются на клиенте для динамического обновления формы. Этот подход является стандартным и обеспечивает корректную работу расширения.

Важные нюансы и рекомендации

Для более глубокого понимания и предотвращения возможных проблем при работе с расширениями и управляемыми формами, рассмотрим несколько важных моментов:

  1. Контекст выполнения кода: `&НаКлиенте` vs. `&НаСервере`

    Мы должны всегда помнить, что код с атрибутом &НаКлиенте имеет доступ к элементам формы и данным, отображаемым пользователю, а код с &НаСервере имеет доступ к объектам базы данных и выполняет ресурсоемкие операции (запросы, записи). Переменная Объект ведет себя по-разному в этих контекстах: на клиенте это данные формы, на сервере — объект базы данных. Правильное разделение логики — залог стабильной работы.

  2. Тип данных `ДанныеФормыЭлементКоллекции`

    Когда вы вызываете Объект.ИмяТабличнойЧасти.Добавить() на клиенте, в ответ вы получаете объект типа ДанныеФормыЭлементКоллекции. Это специальный объект, который представляет строку в табличной части формы. Именно его свойства вы и должны заполнять. Обычные объекты базы данных или строки ТаблицыЗначений имеют другие методы и свойства, но в контексте формы мы работаем именно с ДанныеФормыЭлементКоллекции.

  3. Бизнес-логика заполнения платежных документов

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

  4. Использование клиент-серверного отладчика

    При возникновении проблем с расширениями и клиент-серверным взаимодействием, незаменимым инструментом является клиент-серверный отладчик 1С:Предприятия. Он позволяет пошагово отслеживать выполнение кода как на клиенте, так и на сервере, просматривать значения всех переменных и объектов. Это поможет вам точно определить, на каком этапе происходит сбой или некорректная передача данных.

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

← На главную