Рассмотрим часто встречающуюся задачу при разработке или доработке конфигураций на платформе 1С: Предприятие. Проанализируем ситуацию, когда в документе есть табличная часть с товарами, и нам необходимо программно перебрать ее, чтобы для каждой единицы товара создать отдельную строку и присвоить ей уникальную серию. Посмотрим на пример: изначально у нас есть строка «Товар 1» в количестве 3 штук. После выполнения нашего алгоритма табличная часть должна содержать три отдельные строки «Товар 1», каждая с количеством 1 штука, но с разными сериями (серия А, серия Б, серия В).
Разберем по шагам несколько вариантов решения этой задачи, начиная от базового перебора в цикле и заканчивая оптимизированными подходами, которые учитывают архитектуру современных типовых конфигураций (таких как УТ 11, ERP, КА 2) и требования к высокой производительности.
Самый очевидный способ решения — это выгрузить текущие данные табличной части в промежуточную таблицу значений, очистить исходную табличную часть на форме, а затем в двойном цикле (по строкам и по количеству товара) заполнить ее заново. Рассмотрим пример кода, который часто приходит на ум первым:
ВремТ = Товары.Выгрузить();
Товары.Очистить();
Для каждого СтрокаТЧ из ВремТ Цикл
Для Итератор = 1 по СтрокаТЧ.Количество Цикл
НовСтр = Товары.Добавить();
ЗаполнитьЗначенияСвойств(НовСтр, СтрокаТЧ);
НовСтр.Серия = ВычислитьСериюПоТоваруИНомеру(НовСтр.Товар, Итератор);
КонецЦикла;
КонецЦикла;
Данный код логически понятен, однако в нем кроется критическая ошибка. Метод ЗаполнитьЗначенияСвойств переносит значения всех одноименных колонок исходной строки в новую. Это означает, что вместе с номенклатурой перенесется и исходная колонка «Количество». Если в исходной строке было 3 единицы товара, алгоритм создаст три новые строки, но скопирует в каждую из них количество равное трем. В итоге общее количество товара в документе неконтролируемо умножится и станет равным 9.
Выясним причину и исправим эту логическую ошибку. Чтобы избежать задвоения остатков и искажения сумм, внутри цикла после заполнения свойств необходимо принудительно устанавливать единицу измерения, а также пересчитывать сумму в строке (если такая колонка существует). Исправленный фрагмент будет выглядеть так:
// ... код до цикла ...
НовСтр = Товары.Добавить();
ЗаполнитьЗначенияСвойств(НовСтр, СтрокаТЧ);
// Явно указываем количество для разбитой строки
НовСтр.Количество = 1;
// Если есть колонка Сумма, пересчитываем ее (например, через Цену)
Если ЗначениеЗаполнено(НовСтр.Цена) Тогда
НовСтр.Сумма = НовСтр.Цена * НовСтр.Количество;
КонецЕсли;
НовСтр.Серия = ВычислитьСериюПоТоваруИНомеру(НовСтр.Товар, Итератор);
// ... конец цикла ...
Проанализируем производительность предыдущего решения. Добавление строк непосредственно в табличную часть формы (через Товары.Добавить()) заставляет платформу каждый раз перерисовывать элементы интерфейса. Если строк в документе много, интерфейс 1С может зависнуть. Расход тактов процессора в этом случае неэффективен.
Чтобы ускорить процесс, мы можем работать исключительно в оперативной памяти сервера (или клиента), не затрагивая визуальную часть до самого конца. Для этого мы создаем пустую таблицу значений с нужной структурой колонок, заполняем ее, и в самом конце загружаем целиком в табличную часть.
Загрузить() для переноса данных обратно в документ одним пакетом.
ВремТ = Товары.Выгрузить(); // Исходные данные
ТаблицаРезультат = ВремТ.СкопироватьКолонки(); // Пустая таблица нужной структуры
Для каждого СтрокаТЧ из ВремТ Цикл
Для Итератор = 1 по СтрокаТЧ.Количество Цикл
НовСтр = ТаблицаРезультат.Добавить();
ЗаполнитьЗначенияСвойств(НовСтр, СтрокаТЧ);
НовСтр.Количество = 1;
// Пересчет суммы при необходимости
НовСтр.Серия = ВычислитьСериюПоТоваруИНомеру(НовСтр.Товар, Итератор);
КонецЦикла;
КонецЦикла;
// Загружаем результат обратно в табличную часть формы (выполняется мгновенно)
Товары.Загрузить(ТаблицаРезультат);
Если счет строк идет на сотни или тысячи, вложенные циклы остаются не самым быстрым вариантом. Рассмотрим декларативный подход с использованием языка запросов 1С. Запрос способен "размножить" строки быстрее и элегантнее.
Суть метода заключается в следующем:
ВНУТРЕННЕЕ СОЕДИНЕНИЕ нашей таблицы товаров с таблицей чисел по условию ТаблицаЧисел.Значение <= ТаблицаТоваров.Количество.В результате такого соединения запрос вернет ровно столько одинаковых строк товара, какое количество было указано в исходной строке. Нам останется только выгрузить результат запроса и загрузить его в табличную часть Товары.Загрузить(РезультатЗапроса). Это избавит нас от необходимости писать вложенные циклы.
Проанализируем важный архитектурный нюанс. Метод с физическим размножением строк в таблице «Товары» отлично подходил для старых решений (УТ 10.3, УПП). В современных типовых продуктах 1С логика учета по сериям кардинально изменена.
Важно: Как правило, строка с товаром в документе ERP или УТ 11 не разбивается. В табличной части «Товары» остается одна общая строка (например, с количеством 3). Сами серии заносятся в отдельную (подчиненную) табличную часть, которая может называться Серии или СерииТоваров. Связь между основной строкой товара и таблицей серий обеспечивается через специальный числовой реквизит КлючСвязи.
Если вы работаете в современной базе, вам, скорее всего, нужно не плодить строки с товарами, а сгенерировать записи во вторую табличную часть. Алгоритм будет выглядеть иначе:
// Примерный алгоритм для современных баз
Для каждого СтрокаТовара Из Объект.Товары Цикл
// Проверяем, ведется ли учет по сериям для данного товара
Если УчетПоСериям(СтрокаТовара.Номенклатура) Тогда
Для Итератор = 1 По СтрокаТовара.Количество Цикл
НоваяСерия = Объект.Серии.Добавить();
НоваяСерия.КлючСвязи = СтрокаТовара.КлючСвязи;
НоваяСерия.Количество = 1;
НоваяСерия.Серия = СгенерироватьСерию(СтрокаТовара.Номенклатура);
КонецЦикла;
КонецЕсли;
КонецЦикла;
Обратим внимание на функцию, которая генерирует новую серию. Если серий еще нет в базе данных и вам необходимо программно создавать элементы справочника (используя метод Справочники.СерииНоменклатуры.СоздатьЭлемент()), делать это внутри цикла по строкам табличной части — крайне плохая практика. Каждое обращение к методам записи будет "дергать" базу данных и вызывать просадки производительности.
Правильный подход:
НачатьТранзакцию().ЗафиксироватьТранзакцию().И последнее, на что стоит обратить внимание. Если ваша обработка табличной части вызывается пользователем по кнопке непосредственно из формы документа, то после очистки и загрузки новых данных программно, платформа 1С может не понять, что данные были изменены. При попытке закрыть окно крестиком, 1С не спросит пользователя о сохранении, и все сгенерированные серии будут потеряны.
Чтобы этого избежать, в самом конце вашей процедуры на клиенте всегда устанавливайте флаг модифицированности формы. Это сообщит системе, что данные объекта изменились:
ЭтаФорма.Модифицированность = Истина;
Применяя эти подходы комплексно, мы можем не только решить задачу перебора и добавления серий, но и сделать наш код надежным, быстрым и соответствующим стандартам разработки на платформе 1С.