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