При автоматизации загрузки данных в 1С часто возникает задача не только создать элементы справочников, например, номенклатуру, но и сразу установить для них цены. Стандартный механизм для этого — запись данных в регистр сведений «Цены номенклатуры». Однако при попытке прямой записи в регистр можно столкнуться с ошибкой, когда система не позволяет установить отбор по регистратору. Разберем, почему так происходит и как правильно выполнить эту задачу.
Проанализируем ситуацию: из файла загружаются данные, в цикле создается или обновляется номенклатура, и сразу после этого делается попытка записать для нее цены напрямую в регистр.
Пример кода, который приводит к ошибке:
&НаСервере
Процедура ЗаписатьДанныеНаСервере()
Для Каждого СтрокаДанных Из Объект.ДанныеФайла Цикл
// ... код создания/поиска номенклатуры ...
Номенклатура.Записать();
НаборЗаписей = РегистрыСведений.Цены.СоздатьНаборЗаписей();
// Вот здесь возникает ошибка:
НаборЗаписей.Отбор.Регистратор.Установить(Номенклатура);
НоваяЗапись = НаборЗаписей.Добавить();
НоваяЗапись.Номенклатура = Номенклатура.Ссылка; // Предположим, что здесь Ссылка
НоваяЗапись.РозничнаяЦена = СтрокаДанных.STANDARDFOBCNY;
НоваяЗапись.ЗакупочнаяЦена = СтрокаДанных.FOBCNY;
НаборЗаписей.Записать();
КонецЦикла;
КонецПроцедуры
Ошибка в строке НаборЗаписей.Отбор.Регистратор.Установить(Номенклатура) возникает по фундаментальной причине, связанной с архитектурой регистров сведений в 1С.
В 1С регистры сведений бывают двух основных типов: независимые и подчиненные регистратору. Регистр «Цены номенклатуры» в большинстве типовых конфигураций является подчиненным.
Регистратор — это объект (почти всегда Документ), который создает движения или записи в регистрах. Элемент справочника Номенклатура — это объект учета, но не событие, которое меняет цены. Поэтому система и выдает ошибку: она ожидает получить ссылку на документ, а получает ссылку на справочник.
Методологически верный способ установить цены — это смоделировать действия пользователя. Пользователь не пишет напрямую в регистр, он создает документ «Установка цен номенклатуры», заполняет его и проводит. При проведении платформа сама создает необходимые записи в регистре сведений.
Рассмотрим по шагам, как это реализовать программно.
Давайте исправим процедуру СоздатьДокументНаСервере из первоначального примера, чтобы она корректно выполняла эту логику. Обратите внимание, что мы объединим логику создания номенклатуры и установки цен в одной процедуре для наглядности.
&НаСервере
Процедура ЗагрузитьДанныеИСоздатьДокументЦен()
// 1. Создаем документ ПЕРЕД циклом
НовыйДокумент = Документы.УстановкаЦенНоменклатуры.СоздатьДокумент();
НовыйДокумент.Дата = ТекущаяДата();
// Возможно, потребуется заполнить и другие реквизиты, например, ТипЦен, если он в шапке
Для Каждого СтрокаДанных Из Объект.ДанныеФайла Цикл
// Блок поиска или создания номенклатуры (как в исходном коде)
Наименование = СтрокаДанных.ITEMNO + "| " + СтрокаДанных.GENDER + "| " + СтрокаДанных.DESCRIPTION;
НоменклатураСсылка = Справочники.Номенклатура.НайтиПоНаименованию(Наименование, Истина);
Если НЕ ЗначениеЗаполнено(НоменклатураСсылка) Тогда
НоменклатураОбъект = Справочники.Номенклатура.СоздатьЭлемент();
НоменклатураОбъект.Артикул = СтрокаДанных.ITEMNO;
НоменклатураОбъект.Наименование = Наименование;
// ... другие реквизиты ...
НоменклатураОбъект.Записать();
НоменклатураСсылка = НоменклатураОбъект.Ссылка;
КонецЕсли;
// 2. Заполняем табличную часть документа ВНУТРИ цикла
// ВНИМАНИЕ: Названия ТЧ и колонок могут отличаться в вашей конфигурации!
// Проверьте их в конфигураторе (например, "Товары", а не "ПереченьНоменклатуры")
// ВАЖНО! В типовых конфигурациях обычно есть измерение "Тип цен"
// Поэтому на одну номенклатуру нужно добавить несколько строк в документ, по одной на каждый тип цены
// Добавляем закупочную цену
НоваяСтрокаЗакуп = НовыйДокумент.Товары.Добавить();
НоваяСтрокаЗакуп.Номенклатура = НоменклатураСсылка;
НоваяСтрокаЗакуп.ТипЦен = Справочники.ТипыЦенНоменклатуры.НайтиПоНаименованию("Закупочная"); // Пример!
НоваяСтрокаЗакуп.Цена = СтрокаДанных.FOBCNY;
// Добавляем розничную цену
НоваяСтрокаРозница = НовыйДокумент.Товары.Добавить();
НоваяСтрокаРозница.Номенклатура = НоменклатураСсылка;
НоваяСтрокаРозница.ТипЦен = Справочники.ТипыЦенНоменклатуры.НайтиПоНаименованию("Розничная"); // Пример!
НоваяСтрокаРозница.Цена = СтрокаДанных.STANDARDFOBCNY;
КонецЦикла;
// 3. Записываем и проводим документ ПОСЛЕ цикла
Попытка
НовыйДокумент.Записать(РежимЗаписиДокумента.Проведение);
Сообщить("Документ установки цен №" + НовыйДокумент.Номер + " от " + НовыйДокумент.Дата + " успешно создан и проведен!");
Исключение
Сообщить("Не удалось провести документ установки цен. Описание ошибки: " + ОписаниеОшибки());
КонецПопытки;
КонецПроцедуры
Важный момент: этот код решает сразу две проблемы исходного примера:
Номенклатура.Записать(), НаборЗаписей.Записать()). Это крайне медленно и создает избыточную нагрузку на базу данных. В исправленном варианте документ записывается всего один раз.Хотя в данной задаче прямая запись в регистр была неверным подходом, полезно знать, как правильно работать с набором записей в других ситуациях (например, при работе с независимыми регистрами).
Если вы хотите не просто добавить новые записи, а изменить существующие или добавить к ним, обязательно сначала прочитайте то, что уже есть в регистре по вашему отбору.
НаборЗаписей = РегистрыСведений.КурсыВалют.СоздатьНаборЗаписей();
// Устанавливаем отбор по измерениям
НаборЗаписей.Отбор.Валюта.Установить(СсылкаНаВалюту);
// Для периодических регистров обязательно устанавливаем отбор по периоду
НаборЗаписей.Отбор.Период.Установить(НачалоДня(ТекущаяДата()));
// Читаем существующие записи
НаборЗаписей.Прочитать();
// Проверяем, есть ли уже запись на эту дату
Если НаборЗаписей.Количество() = 0 Тогда
// Записи нет, добавляем новую
НоваяЗапись = НаборЗаписей.Добавить();
НоваяЗапись.Период = ТекущаяДата();
НоваяЗапись.Валюта = СсылкаНаВалюту;
Иначе
// Запись уже есть, получаем ее для изменения
НоваяЗапись = НаборЗаписей[0];
КонецЕсли;
// Устанавливаем значения ресурсов
НоваяЗапись.Курс = 100;
НоваяЗапись.Кратность = 1;
// Записываем весь набор (старые записи будут заменены новыми)
НаборЗаписей.Записать();
Если вы не уверены, какой тип данных ожидает поле отбора, можно проверить это в отладчике. Выполните выражение:
НаборЗаписей.Отбор.Регистратор.ТипЗначения
Это покажет вам объект ОписаниеТипов, в котором перечислены все допустимые типы данных для этого поля. Это поможет быстро понять, что вы передаете СправочникСсылка, а система ожидает ДокументСсылка.
Подводя итог, для установки цен и других операций, которые в системе фиксируются документами, всегда используйте программное создание и проведение этих документов. Это не только правильно с точки зрения архитектуры 1С, но и более надежно, производительно и гарантирует целостность данных.