Как программно заполнить табличную часть документа «Установка цен номенклатуры» без эффекта «лесенки»

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

При автоматизации бизнес-процессов в конфигурациях на обычных формах (таких как «Управление торговлей 10.3», «Комплексная автоматизация 1.1» или «Управление производственным предприятием 1.3») разработчикам часто приходится программно создавать и заполнять документ Установка цен номенклатуры. Однако при простом переборе и добавлении строк в табличную часть Товары программисты сталкиваются с неприятным визуальным багом: при открытии созданного документа номенклатура и типы цен отображаются в виде «лесенки». Каждый тип цен для одного и того же товара выводится на новой строке со смещением, хотя при интерактивном заполнении все цены по одной позиции красиво группируются в одну строку с несколькими колонками.

В этой статье мы подробно разберем, почему возникает этот визуальный дефект, как устроена внутренняя архитектура хранения цен в документе, и рассмотрим несколько эффективных способов программного «схлопывания» табличной части.

Архитектура хранения данных в документе «Установка цен номенклатуры»

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

  1. Физические таблицы (в базе данных): Документ хранит сведения в плоском виде. В нем есть две ключевые табличные части. Первая — ТипыЦен, где перечисляются все типы цен, которые устанавливаются данным документом (например, «Закупочная», «Оптовая», «Розничная»). Вторая — Товары, которая содержит плоский список строк. Для каждого товара и каждого устанавливаемого типа цены в базе создается отдельная полноценная физическая строка. Если вы устанавливаете 3 типа цен для одного товара, в табличной части Товары физически запишутся 3 строки.
  2. Экранная таблица (на форме): Пользователю неудобно работать с плоской таблицей, поэтому при открытии формы документа встроенные процедуры динамически перестраивают данные. На форме создается виртуальная таблица значений (в коде типовых конфигураций она называется ТаблицаЦен), где строки — это уникальные товары, а колонки — динамически добавленные типы цен.

Связующим звеном между плоской физической таблицей Товары и экранной формой выступает служебный реквизит табличной части — ИндексСтрокиТаблицыЦен. Именно по значению этого числового реквизита алгоритм формы определяет, какие физические строки относятся к одной и той же экранной строке. Если при программном создании документа оставить этот реквизит пустым (или равным значению по умолчанию -1), механизм построения формы не сможет сгруппировать строки по товарам. В результате мы получим визуальную «лесенку» из цен.

Способ 1. Простой перерасчет индекса через сортировку и цикл (базовый вариант)

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

Разберем по шагам реализацию этого метода:


// Предположим, у нас есть объект документа "УстЦен"
// Перед расчетом обязательно сортируем физическую ТЧ по номенклатуре и типам цен
УстЦен.Товары.Сортировать("Номенклатура, ТипЦен");

ИндексСтрокиТаблицыЦен = -1;
ТекущаяНоменклатура = Неопределено;

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

// Записываем документ с сохранением изменений в ИБ
УстЦен.Записать(РежимЗаписиДокумента.Запись);

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

Способ 2. Обязательный учет характеристик номенклатуры

Выясним причину, почему базовый вариант кода может исказить данные. Если в программе ведется учет по характеристикам номенклатуры, то один и тот же товар с разными характеристиками (например, «Обувь мужская» с размерами «41» и «42») должен выводиться на экран в разных строках.

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

Проанализируем правильный вариант кода, учитывающий характеристики:


// Сортируем табличную часть с обязательным учетом характеристик
УстЦен.Товары.Сортировать("Номенклатура, ХарактеристикаНоменклатуры, ТипЦен");

ИндексСтрокиТаблицыЦен = -1;
ТекущаяНоменклатура = Неопределено;
ТекущаяХарактеристика = Неопределено;

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

УстЦен.Записать(РежимЗаписиДокумента.Запись);

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

Способ 3. Использование промежуточной Таблицы Значений (наиболее надежный метод)

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

Разберем алгоритм по шагам:

  1. Выгрузить табличную часть Товары во временную таблицу значений.
  2. Свернуть таблицу по уникальным аналитическим разрезам (Номенклатура и ХарактеристикаНоменклатуры), чтобы получить уникальный список выводимых строк.
  3. Добавить колонку ИндексСтрокиТаблицыЦен и заполнить ее простым счетчиком строк от 0 до N.
  4. Проиндексировать временную таблицу по ключевым полям для быстрого поиска.
  5. Заполнить индексы в исходной табличной части документа поиском по временной таблице.

Посмотрим на пример кода, реализующий эту схему:


// Шаг 1. Выгружаем данные табличной части документа в ТЗ
ТаблицаТоваров = УстЦен.Товары.Выгрузить();

// Шаг 2. Копируем таблицу и сворачиваем ее по уникальным разрезам учета
ТаблицаУникальныхСтрок = ТаблицаТоваров.Скопировать();
ТаблицаУникальныхСтрок.Свернуть("Номенклатура, ХарактеристикаНоменклатуры");

// Шаг 3. Добавляем колонку индекса и заполняем ее порядковыми номерами
ТаблицаУникальныхСтрок.Колонки.Добавить("ИндексСтрокиТаблицыЦен", Новый ОписаниеТипов("Число"));
Для Индекс = 0 По ТаблицаУникальныхСтрок.Количество() - 1 Цикл
    ТаблицаУникальныхСтрок[Индекс].ИндексСтрокиТаблицыЦен = Индекс;
КонецЦикла;

// Шаг 4. Индексируем вспомогательную таблицу для ускорения поиска методом НайтиСтроки()
ТаблицаУникальныхСтрок.Индексы.Добавить("Номенклатура, ХарактеристикаНоменклатуры");

// Шаг 5. Переносим рассчитанные индексы обратно в физическую ТЧ документа
Для каждого СтрокаТовара Из УстЦен.Товары Цикл
    Отбор = Новый Структура;
    Отбор.Вставить("Номенклатура", СтрокаТовара.Номенклатура);
    Отбор.Вставить("ХарактеристикаНоменклатуры", СтрокаТовара.ХарактеристикаНоменклатуры);
    
    НайденныеСтроки = ТаблицаУникальныхСтрок.НайтиСтроки(Отбор);
    Если НайденныеСтроки.Количество() > 0 Тогда
        СтрокаТовара.ИндексСтрокиТаблицыЦен = НайденныеСтроки[0].ИндексСтрокиТаблицыЦен;
    КонецЕсли;
КонецЦикла;

УстЦен.Записать(РежимЗаписиДокумента.Запись);

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

Способ 4. Математический расчет по количеству типов цен

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

ИндексСтрокиТаблицыЦен = ЦелаяЧасть((НомерСтроки - 1) / КоличествоТиповЦен)

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

Типичные ошибки при некорректном заполнении индексов

Если индексы строк заполнены с ошибками или дублируются для разных товаров, при попытке открыть форму документа или провести его система выдаст стандартные системные ошибки:

Использование правильных подходов к расчету индекса строки таблицы цен позволит вам создавать красивые, корректные документы «Установка цен номенклатуры» программным путем, которые будут открываться у пользователей без ошибок и лишних задержек!

← На главную