При автоматизации бизнес-процессов в конфигурациях на обычных формах (таких как «Управление торговлей 10.3», «Комплексная автоматизация 1.1» или «Управление производственным предприятием 1.3») разработчикам часто приходится программно создавать и заполнять документ Установка цен номенклатуры. Однако при простом переборе и добавлении строк в табличную часть Товары программисты сталкиваются с неприятным визуальным багом: при открытии созданного документа номенклатура и типы цен отображаются в виде «лесенки». Каждый тип цен для одного и того же товара выводится на новой строке со смещением, хотя при интерактивном заполнении все цены по одной позиции красиво группируются в одну строку с несколькими колонками.
В этой статье мы подробно разберем, почему возникает этот визуальный дефект, как устроена внутренняя архитектура хранения цен в документе, и рассмотрим несколько эффективных способов программного «схлопывания» табличной части.
Для понимания сути проблемы проанализируем, как документ хранит информацию в базе данных и как он выводит ее на экран пользователю. Здесь реализовано разделение между физической структурой хранения и виртуальным интерфейсом формы:
ТипыЦен, где перечисляются все типы цен, которые устанавливаются данным документом (например, «Закупочная», «Оптовая», «Розничная»). Вторая — Товары, которая содержит плоский список строк. Для каждого товара и каждого устанавливаемого типа цены в базе создается отдельная полноценная физическая строка. Если вы устанавливаете 3 типа цен для одного товара, в табличной части Товары физически запишутся 3 строки.ТаблицаЦен), где строки — это уникальные товары, а колонки — динамически добавленные типы цен.Связующим звеном между плоской физической таблицей Товары и экранной формой выступает служебный реквизит табличной части — ИндексСтрокиТаблицыЦен. Именно по значению этого числового реквизита алгоритм формы определяет, какие физические строки относятся к одной и той же экранной строке. Если при программном создании документа оставить этот реквизит пустым (или равным значению по умолчанию -1), механизм построения формы не сможет сгруппировать строки по товарам. В результате мы получим визуальную «лесенку» из цен.
Самый очевидный способ восстановить правильные индексы строк без открытия формы документа — это отсортировать табличную часть Товары по номенклатуре и последовательно присвоить одинаковые индексы строкам, относящимся к одному товару.
Разберем по шагам реализацию этого метода:
// Предположим, у нас есть объект документа "УстЦен"
// Перед расчетом обязательно сортируем физическую ТЧ по номенклатуре и типам цен
УстЦен.Товары.Сортировать("Номенклатура, ТипЦен");
ИндексСтрокиТаблицыЦен = -1;
ТекущаяНоменклатура = Неопределено;
// Обходим табличную часть последовательно
Для каждого ТекСтрока Из УстЦен.Товары Цикл
// Если номенклатура в текущей строке отличается от предыдущей,
// значит, начинается новый товар, и мы увеличиваем индекс строки формы
Если ТекущаяНоменклатура <> ТекСтрока.Номенклатура Тогда
ИндексСтрокиТаблицыЦен = ИндексСтрокиТаблицыЦен + 1;
КонецЕсли;
// Записываем рассчитанный индекс в служебный реквизит строки
ТекСтрока.ИндексСтрокиТаблицыЦен = ИндексСтрокиТаблицыЦен;
// Запоминаем номенклатуру текущей строки для сравнения на следующей итерации
ТекущаяНоменклатура = ТекСтрока.Номенклатура;
КонецЦикла;
// Записываем документ с сохранением изменений в ИБ
УстЦен.Записать(РежимЗаписиДокумента.Запись);
Этот способ прост и эффективен, однако он корректно работает только в том случае, если в вашей информационной базе не используется учет по характеристикам номенклатуры.
Выясним причину, почему базовый вариант кода может исказить данные. Если в программе ведется учет по характеристикам номенклатуры, то один и тот же товар с разными характеристиками (например, «Обувь мужская» с размерами «41» и «42») должен выводиться на экран в разных строках.
Если применить базовый алгоритм, программа сравнит только реквизит Номенклатура, проигнорирует характеристики, присвоит им один и тот же индекс, и строки сольются на форме в одну. Это приведет к утере данных и ошибкам проведения.
Проанализируем правильный вариант кода, учитывающий характеристики:
// Сортируем табличную часть с обязательным учетом характеристик
УстЦен.Товары.Сортировать("Номенклатура, ХарактеристикаНоменклатуры, ТипЦен");
ИндексСтрокиТаблицыЦен = -1;
ТекущаяНоменклатура = Неопределено;
ТекущаяХарактеристика = Неопределено;
Для каждого ТекСтрока Из УстЦен.Товары Цикл
// Условие изменения строки теперь проверяет и товар, и его характеристику
Если (ТекущаяНоменклатура <> ТекСтрока.Номенклатура)
Или (ТекущаяХарактеристика <> ТекСтрока.ХарактеристикаНоменклатуры) Тогда
ИндексСтрокиТаблицыЦен = ИндексСтрокиТаблицыЦен + 1;
КонецЕсли;
ТекСтрока.ИндексСтрокиТаблицыЦен = ИндексСтрокиТаблицыЦен;
// Сохраняем значения для сравнения на следующем шаге цикла
ТекущаяНоменклатура = ТекСтрока.Номенклатура;
ТекущаяХарактеристика = ТекСтрока.ХарактеристикаНоменклатуры;
КонецЦикла;
УстЦен.Записать(РежимЗаписиДокумента.Запись);
Если на вашем предприятии дополнительно используется учет по сериям номенклатуры, то поле СерияНоменклатуры необходимо добавить в сортировку и в условие сравнения строк аналогично характеристикам.
Рассмотрим более надежный и профессиональный подход, который исключает ручное сравнение предыдущих и текущих строк в цикле и гарантирует высокую производительность на больших объемах данных. Этот метод основан на сворачивании временной таблицы значений.
Разберем алгоритм по шагам:
Товары во временную таблицу значений.Номенклатура и ХарактеристикаНоменклатуры), чтобы получить уникальный список выводимых строк.ИндексСтрокиТаблицыЦен и заполнить ее простым счетчиком строк от 0 до N.Посмотрим на пример кода, реализующий эту схему:
// Шаг 1. Выгружаем данные табличной части документа в ТЗ
ТаблицаТоваров = УстЦен.Товары.Выгрузить();
// Шаг 2. Копируем таблицу и сворачиваем ее по уникальным разрезам учета
ТаблицаУникальныхСтрок = ТаблицаТоваров.Скопировать();
ТаблицаУникальныхСтрок.Свернуть("Номенклатура, ХарактеристикаНоменклатуры");
// Шаг 3. Добавляем колонку индекса и заполняем ее порядковыми номерами
ТаблицаУникальныхСтрок.Колонки.Добавить("ИндексСтрокиТаблицыЦен", Новый ОписаниеТипов("Число"));
Для Индекс = 0 По ТаблицаУникальныхСтрок.Количество() - 1 Цикл
ТаблицаУникальныхСтрок[Индекс].ИндексСтрокиТаблицыЦен = Индекс;
КонецЦикла;
// Шаг 4. Индексируем вспомогательную таблицу для ускорения поиска методом НайтиСтроки()
ТаблицаУникальныхСтрок.Индексы.Добавить("Номенклатура, ХарактеристикаНоменклатуры");
// Шаг 5. Переносим рассчитанные индексы обратно в физическую ТЧ документа
Для каждого СтрокаТовара Из УстЦен.Товары Цикл
Отбор = Новый Структура;
Отбор.Вставить("Номенклатура", СтрокаТовара.Номенклатура);
Отбор.Вставить("ХарактеристикаНоменклатуры", СтрокаТовара.ХарактеристикаНоменклатуры);
НайденныеСтроки = ТаблицаУникальныхСтрок.НайтиСтроки(Отбор);
Если НайденныеСтроки.Количество() > 0 Тогда
СтрокаТовара.ИндексСтрокиТаблицыЦен = НайденныеСтроки[0].ИндексСтрокиТаблицыЦен;
КонецЕсли;
КонецЦикла;
УстЦен.Записать(РежимЗаписиДокумента.Запись);
Этот метод считается стандартом разработки, так как он легко масштабируется (достаточно просто добавить новое поле в группировку и структуру отбора) и полностью застрахован от ошибок сортировки строк внутри табличной части.
Существует альтернативный математический метод расчета, основанный на количестве типов цен в документе. Если для каждой номенклатурной позиции в документе строго заполняются все выбранные типы цен (без пропусков), то индекс строки экранной формы можно рассчитать по формуле:
ИндексСтрокиТаблицыЦен = ЦелаяЧасть((НомерСтроки - 1) / КоличествоТиповЦен)
Важное предупреждение: Данный метод крайне опасен в практическом применении. Если для какого-то товара в документе не окажется одного из типов цен (например, у товара нет закупочной цены, поэтому соответствующая строка не создана), математический расчет собьется, индексы сместятся, и документ отобразится некорректно. Настоятельно рекомендуем использовать Способ 3 как наиболее стабильный и отказоустойчивый.
Если индексы строк заполнены с ошибками или дублируются для разных товаров, при попытке открыть форму документа или провести его система выдаст стандартные системные ошибки:
Поле объекта не обнаружено (цена...) — возникает в процедурах модуля формы при попытке динамического сопоставления физических строк с виртуальными колонками цен.В строке номер "Х" табличной части для типа цен "" не заполнена Номенклатура / Валюта — ошибка валидации при интерактивной записи, когда нарушена логическая связь между строками из-за некорректных значений в реквизите ИндексСтрокиТаблицыЦен.Использование правильных подходов к расчету индекса строки таблицы цен позволит вам создавать красивые, корректные документы «Установка цен номенклатуры» программным путем, которые будут открываться у пользователей без ошибок и лишних задержек!