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