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

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

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

Анализируем исходную задачу

Представим ситуацию: у нас есть документ с табличной частью, в которой пользователь указывает список материалов. Одна из колонок этой табличной части предназначена для отображения текущего остатка каждого материала на складе. Наша цель — при нажатии кнопки или при открытии документа автоматически заполнить или обновить эту колонку "Остаток" актуальными данными из базы данных. Запрос для получения этих остатков для указанных материалов у нас уже есть и работает. Основная сложность заключается в правильной подстановке полученных значений в соответствующие строки табличной части таким образом, чтобы для каждого материала отобразился именно его остаток, и при этом не были утеряны другие данные, уже введенные в эту табличную часть.

Метод 1: Итеративное обновление с использованием поиска по ключевому полю (рекомендуемый подход)

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

  1. Шаг 1: Формирование запроса для получения остатков.

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

    Пример такого оптимизированного запроса:

    
    ВЫБРАТЬ
        МатериалыОстатки.Материал КАК Материал,
        МатериалыОстатки.КоличествоОстаток КАК Остаток
    ИЗ
        РегистрНакопления.ОстаткиМатериалов.Остатки(
            &МоментВремени,
            Материал В
                (ВЫБРАТЬ РАЗЛИЧНЫЕ
                    ТабличнаяЧастьДокумента.Материал
                ИЗ
                    Документ.НашДокумент.ТабличнаяЧастьДокумента КАК ТабличнаяЧастьДокумента
                ГДЕ
                    ТабличнаяЧастьДокумента.Ссылка = &СсылкаДокумента)
        ) КАК МатериалыОстатки
    

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

  2. Шаг 2: Выполнение запроса и получение таблицы значений.

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

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

    Если предполагается, что табличная часть документа и, соответственно, результат запроса могут содержать очень большое количество строк, для ускорения операции поиска в ТаблицеЗначений, полученной из запроса, мы можем создать индекс по ключевой колонке (в нашем случае по Материал). Это позволит методу НайтиСтроки() работать значительно быстрее, так как поиск будет осуществляться по индексированным данным.

    
    // После получения РезультатЗапроса:
    Если РезультатЗапроса.Количество() > 0 И Не РезультатЗапроса.Индексы.Найти("Материал") Тогда
        РезультатЗапроса.Индексы.Добавить("Материал");
    КонецЕсли;
    
  4. Шаг 4: Итерация по строкам табличной части и подстановка значений.

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

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

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

  5. Шаг 5: Обновление формы (если необходимо).

    После того как мы обновили табличную часть на сервере, в большинстве случаев управляемая форма автоматически обновит свое отображение, и пользователь сразу увидит изменения. Однако, если вы заметите, что изменения не отображаются, можно принудительно обновить данные формы. Для реквизитов формы, которые являются "Объектом" (например, сам документ), этого обычно не требуется, поскольку платформа сама отслеживает изменения. В некоторых специфичных случаях может потребоваться вызвать метод формы ОбновитьОтображениеДанных(), но это скорее исключение.

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

Метод 2: Полная перезагрузка табличной части через ТЧ.Загрузить(ТЗ) (с осторожностью!)

Метод ТЧ.Загрузить(ТЗ) позволяет загрузить данные из объекта ТаблицаЗначений в табличную часть документа. Это кажется очень простым и лаконичным решением, но требует крайней осторожности при его использовании.

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

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


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

Даже если вы хотите заполнить только "Материал" и "Остаток", но в ТЧ есть другие, не пустые колонки, которые вы хотите сохранить, этот метод все равно перезапишет их пустыми значениями (если таких колонок нет в РезультатЗапроса) или значениями из РезультатЗапроса, что может привести к потере данных и некорректной работе документа.

Метод 3: Очистка табличной части и повторное заполнение (похож на Метод 2)

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

Предупреждение: Как и в случае с ТЧ.Загрузить(ТЗ), данный метод приведет к потере всех ранее заполненных данных в табличной части. Если вам нужно сохранить какие-либо данные, вам придется сначала выгрузить их во временное хранилище, очистить табличную часть, а затем вручную восстановить нужные колонки при добавлении новых строк.


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

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

Важные аспекты клиент-серверного взаимодействия

При работе в управляемых формах 1С:Предприятие 8.3 крайне важно понимать разницу между кодом, выполняемым на клиенте (на компьютере пользователя), и кодом, выполняемым на сервере (на сервере 1С:Предприятия). Неправильное понимание этого может привести к ошибкам "Поле объекта не обнаружено" или "Метод объекта не доступен на клиенте".

  1. Запросы к базе данных: Все запросы к базе данных, а также непосредственная работа с объектами документов (включая их табличные части), всегда должны выполняться на сервере. Это обеспечивается специальными аннотациями, такими как &НаСервере или &НаСервереБезКонтекста, перед определением процедуры или функции. Попытка выполнить запрос или изменить объект документа на клиенте приведет к ошибке.
  2. Вызов серверных процедур: Если кнопка для заполнения остатков находится на клиенте (что характерно для управляемых форм), она должна вызывать серверную процедуру или функцию. Клиентский код просто инициирует выполнение серверного кода, который и делает всю "тяжелую" работу с данными.
  3. Передача данных: Объекты ТаблицаЗначений напрямую не могут быть переданы с клиента на сервер (и наоборот) без предварительной сериализации (например, в XML или JSON, или через специальные механизмы передачи данных форм). Однако, в нашем сценарии запрос выполняется на сервере, и полученная ТаблицаЗначений остается на сервере, где и происходит обновление табличной части документа. После того как объект документа изменяется на сервере, платформа автоматически передает эти изменения на клиент, и форма обновляется для отображения актуальных данных. Это происходит "под капотом" платформы, и обычно вам не нужно явно беспокоиться о передаче всей ТаблицыЗначений между клиентом и сервером.

Рекомендации и альтернативные подходы

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

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

Заключение: Выбор конкретного метода подстановки значений из запроса в табличную часть документа зависит от специфических требований к документу и его функциональности. В большинстве случаев, когда требуется сохранить существующие данные в табличной части, итеративное обновление с поиском по ключевому полю является наиболее предпочтительным и безопасным решением, обеспечивающим гибкость, целостность данных и хорошую производительность. Методы полной перезагрузки (ТЧ.Загрузить() или ТЧ.Очистить() с последующим добавлением) следует использовать только тогда, когда это явно необходимо и не приведет к потере данных.

← На главную