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