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