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

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

Рассмотрим часто встречающуюся задачу при разработке или доработке конфигураций на платформе 1С: Предприятие. Проанализируем ситуацию, когда в документе есть табличная часть с товарами, и нам необходимо программно перебрать ее, чтобы для каждой единицы товара создать отдельную строку и присвоить ей уникальную серию. Посмотрим на пример: изначально у нас есть строка «Товар 1» в количестве 3 штук. После выполнения нашего алгоритма табличная часть должна содержать три отдельные строки «Товар 1», каждая с количеством 1 штука, но с разными сериями (серия А, серия Б, серия В).

Разберем по шагам несколько вариантов решения этой задачи, начиная от базового перебора в цикле и заканчивая оптимизированными подходами, которые учитывают архитектуру современных типовых конфигураций (таких как УТ 11, ERP, КА 2) и требования к высокой производительности.

Базовый алгоритм: использование Таблицы Значений и циклов

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


ВремТ = Товары.Выгрузить(); 
Товары.Очистить();

Для каждого СтрокаТЧ из ВремТ Цикл
    Для Итератор = 1 по СтрокаТЧ.Количество Цикл
        НовСтр = Товары.Добавить();
        ЗаполнитьЗначенияСвойств(НовСтр, СтрокаТЧ);
        НовСтр.Серия = ВычислитьСериюПоТоваруИНомеру(НовСтр.Товар, Итератор);
    КонецЦикла;
КонецЦикла;

Данный код логически понятен, однако в нем кроется критическая ошибка. Метод ЗаполнитьЗначенияСвойств переносит значения всех одноименных колонок исходной строки в новую. Это означает, что вместе с номенклатурой перенесется и исходная колонка «Количество». Если в исходной строке было 3 единицы товара, алгоритм создаст три новые строки, но скопирует в каждую из них количество равное трем. В итоге общее количество товара в документе неконтролируемо умножится и станет равным 9.

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


// ... код до цикла ...
НовСтр = Товары.Добавить();
ЗаполнитьЗначенияСвойств(НовСтр, СтрокаТЧ);

// Явно указываем количество для разбитой строки
НовСтр.Количество = 1;

// Если есть колонка Сумма, пересчитываем ее (например, через Цену)
Если ЗначениеЗаполнено(НовСтр.Цена) Тогда
    НовСтр.Сумма = НовСтр.Цена * НовСтр.Количество;
КонецЕсли;

НовСтр.Серия = ВычислитьСериюПоТоваруИНомеру(НовСтр.Товар, Итератор);
// ... конец цикла ...

Оптимизация алгоритма: снижение нагрузки на интерфейс

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

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

  1. Выгружаем структуру табличной части или создаем пустую ТЗ.
  2. Бежим по исходным данным, добавляем новые строки в нашу виртуальную таблицу значений.
  3. Используем метод Загрузить() для переноса данных обратно в документ одним пакетом.

ВремТ = Товары.Выгрузить(); // Исходные данные
ТаблицаРезультат = ВремТ.СкопироватьКолонки(); // Пустая таблица нужной структуры

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

// Загружаем результат обратно в табличную часть формы (выполняется мгновенно)
Товары.Загрузить(ТаблицаРезультат);

Альтернативный подход: декларативное размножение строк через Запрос

Если счет строк идет на сотни или тысячи, вложенные циклы остаются не самым быстрым вариантом. Рассмотрим декларативный подход с использованием языка запросов 1С. Запрос способен "размножить" строки быстрее и элегантнее.

Суть метода заключается в следующем:

  1. Мы передаем исходную табличную часть в запрос как параметр (временную таблицу).
  2. В запросе создаем (или используем готовую) виртуальную таблицу чисел (например, где числа идут от 1 до 1000).
  3. Выполняем ВНУТРЕННЕЕ СОЕДИНЕНИЕ нашей таблицы товаров с таблицей чисел по условию ТаблицаЧисел.Значение <= ТаблицаТоваров.Количество.

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

Особенности архитектуры современных конфигураций (УТ 11, ERP, КА 2)

Проанализируем важный архитектурный нюанс. Метод с физическим размножением строк в таблице «Товары» отлично подходил для старых решений (УТ 10.3, УПП). В современных типовых продуктах 1С логика учета по сериям кардинально изменена.

Важно: Как правило, строка с товаром в документе ERP или УТ 11 не разбивается. В табличной части «Товары» остается одна общая строка (например, с количеством 3). Сами серии заносятся в отдельную (подчиненную) табличную часть, которая может называться Серии или СерииТоваров. Связь между основной строкой товара и таблицей серий обеспечивается через специальный числовой реквизит КлючСвязи.

Если вы работаете в современной базе, вам, скорее всего, нужно не плодить строки с товарами, а сгенерировать записи во вторую табличную часть. Алгоритм будет выглядеть иначе:


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

Программная генерация новых серий и работа с БД

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

Правильный подход:

  1. Сначала обойти табличную часть и собрать информацию (в массив или соответствие) о том, сколько и каких серий нужно создать для каждого товара.
  2. Открыть явную транзакцию с помощью конструкции НачатьТранзакцию().
  3. Пакетно создать все нужные серии номенклатуры, записать их и поместить в структуру (где ключ — номенклатура, а значение — массив готовых ссылок на серии).
  4. Зафиксировать изменения в базе данных с помощью ЗафиксироватьТранзакцию().
  5. Только после этого перебирать табличную часть (или таблицу значений) на клиенте/сервере, подставляя уже существующие в базе ссылки из заготовленного массива.

Отображение изменений на управляемой форме

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

Чтобы этого избежать, в самом конце вашей процедуры на клиенте всегда устанавливайте флаг модифицированности формы. Это сообщит системе, что данные объекта изменились:

ЭтаФорма.Модифицированность = Истина;

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

← На главную