Как правильно заполнять и обновлять реквизиты подчиненного периодического справочника в 1С 7.7?

Программист 1С v7.7 Управленческий учет IT и автоматизация бизнеса
← На главную

Разработка обработок для загрузки данных из внешних источников (например, Microsoft Excel) — одна из наиболее частых задач программиста 1С. В среде «1С:Предприятие 7.7» (которую ласково называют «клюшками») эта задача имеет свои архитектурные особенности. Особенно часто вопросы возникают при работе со справочниками, имеющими сложную структуру: подчинение и периодические реквизиты. Типичным примером является справочник Цены, который подчинен справочнику Номенклатура.

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

Анализ проблемы: почему новые элементы создаются, а старые не меняются?

Рассмотрим классическую ошибку, с которой часто сталкиваются начинающие разработчики при работе с выборками в 1С 7.7. Посмотрим на исходный фрагмент кода, который выполняет поиск существующей цены:


ЕстьВыборка=СпрЦен.ВыбратьЭлементы();    
Если ЕстьВыборка=0 тогда     // создаем новый
    СпрЦен.Новый(); 
    // Заполнение реквизитов...
    СпрЦен.Записать();  
ИначеЕсли ЕстьВыборка=1 тогда    // меняем просто значение цены на текущую дату
    СпрЦен.Цена=ЦенаХЛ;
    СпрЦен.Записать();
КонецЕсли;

Выясним причину, почему ветка изменения цены срабатывает некорректно. Метод ВыбратьЭлементы() возвращает единицу (1), если в справочнике по заданному фильтру (в данном случае — по владельцу) есть хотя бы один элемент, и ноль (0), если элементов нет.

Однако вызов метода ВыбратьЭлементы() лишь открывает выборку, но не позиционирует объект базы данных на конкретном элементе. Переменная СпрЦен в этот момент указывает на «пустой» элемент справочника. Пытаясь выполнить присвоение СпрЦен.Цена = ЦенаХЛ без предварительного считывания элемента, мы совершаем ошибку: система не понимает, для какого именно элемента нужно изменить реквизит.

Для корректного позиционирования внутри открытой выборки обязательно нужно использовать метод ПолучитьЭлемент(). Разберем правильную структуру обхода выборки по шагам:


СпрЦен.ИспользоватьВладельца(ТМЦ);
Если СпрЦен.ВыбратьЭлементы() = 0 Тогда
    // Если выборка пуста, гарантированно создаем новый элемент
    СпрЦен.Новый();
    СпрЦен.Валюта = Константа.БазоваяВалюта;
    СпрЦен.Единица = ТМЦ.БазоваяЕдиница;
    СпрЦен.КатегорияЦены = Спис;
    СпрЦен.Цена = ЦенаХЛ;
    СпрЦен.Записать();
Иначе
    // Если элементы найдены, обходим их в цикле
    Пока СпрЦен.ПолучитьЭлемент() = 1 Цикл
        // Проверяем соответствие категории цены (если нужно обновить конкретную)
        Если СпрЦен.КатегорияЦены = Спис Тогда
            СпрЦен.Цена = ЦенаХЛ;
            СпрЦен.Записать();
        КонецЕсли;
    КонецЦикла;
КонецЕсли;

Обратите внимание, что в 1С 7.7 отсутствует метод ПолучитьОбъект(), характерный для платформы 1С 8.x. Работа с данными здесь происходит непосредственно через позиционированный объект справочника.

Оптимальное решение: быстрый поиск вместо перебора

Хотя обход выборки через цикл Пока ... ПолучитьЭлемент() = 1 является рабочим решением, его нельзя назвать оптимальным с точки зрения производительности, особенно если у одного товара может быть много типов цен или история изменений слишком велика.

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


СпрЦен.ИспользоватьВладельца(ТМЦ);
// Третий параметр "0" указывает искать только среди элементов данного владельца
Если СпрЦен.НайтиПоРеквизиту("КатегорияЦены", Спис, 0) = 1 Тогда
    // Элемент успешно найден и объект СпрЦен позиционирован на нем.
    // Переходим к обновлению цены.
    СпрЦен.Цена = ЦенаХЛ;
    СпрЦен.Записать();
Иначе
    // Элемент не найден, создаем новую категорию цены для товара
    СпрЦен.Новый();
    СпрЦен.Валюта = Константа.БазоваяВалюта;
    СпрЦен.Единица = ТМЦ.БазоваяЕдиница; // Важно использовать подчиненный справочник единиц
    СпрЦен.КатегорияЦены = Спис;
    СпрЦен.Цена = ЦенаХЛ;
    СпрЦен.Записать();
КонецЕсли;

Важное требование: Для того чтобы метод НайтиПоРеквизиту() работал, у реквизита КатегорияЦены в конфигураторе обязательно должен быть установлен флаг «Сортировка». Без этого индекса поиск вызовет ошибку времени выполнения.

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

В типовых конфигурациях 1С 7.7 (например, «Торговля и Склад») реквизит Цена в справочнике цен является периодическим. Это означает, что его значение хранится в привязке к датам истории. Запись таких реквизитов имеет свои правила.

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

  1. Режим работы с датой через ИспользоватьДату()

    Когда мы вызываем метод СпрЦен.ИспользоватьДату(НужнаяДата), мы временно переводим объект справочника в режим работы, при котором все периодические реквизиты ведут себя как обычные реквизиты. Присвоение СпрЦен.Цена = ЦенаХЛ запишет значение именно на дату, переданную в функцию ИспользоватьДату().

    Важное предостережение: Если вы применили к объекту справочника метод ИспользоватьДату(), использовать метод прямого изменения периодических значений Установить() для этого же объекта категорически запрещено. Код вида СпрЦен.Цена.Установить(Дата, Значение) вызовет фатальную ошибку: «Значение не представляет агрегатный объект».

  2. Прямая запись через метод Установить()

    Если метод ИспользоватьДату() для объекта справочника не вызывался, правильный синтаксис установки периодического значения выглядит следующим образом:

    
    СпрЦен.Цена.Установить(ТекущаяДата(), ЦенаХЛ);
    

    Особенность метода Установить() заключается в том, что он сразу физически записывает значение периодического реквизита в базу данных на указанную дату. При этом вызывать метод всего объекта Записать() не требуется (если только вы параллельно не меняли непериодические реквизиты, такие как Единица или Валюта).

Альтернатива: Использование объекта «Периодический»

Для сложных манипуляций с историей значений и во избежание блокировок транзакций опытные разработчики часто используют специализированный системный объект Периодический. Он работает напрямую со служебной таблицей истории значений. Рассмотрим пример перезаписи цены с помощью этого объекта:


ПериодЦена = СоздатьОбъект("Периодический");
// Связываем периодический объект с конкретным реквизитом конкретного элемента справочника
ПериодЦена.ИспользоватьОбъект("Цена", СпрЦен.ТекущийЭлемент());
ПериодЦена.ДатаЗнач = ТекущаяДата();
ПериодЦена.Значение = ЦенаХЛ;
ПериодЦена.Записать(); 

Такой подход обеспечивает максимальную надежность при обработке больших массивов данных или в распределенных базах.

Внимание на единицы измерения (Проблема "адского трэша")

В типовой конфигурации «Торговля и Склад 7.7» цена товара жестко привязана к единице измерения. Сами единицы измерения (штуки, коробки) хранятся в отдельном справочнике Единицы, который также подчинен справочнику Номенклатура.

Запись в поле Единица простого текста или неверной ссылки (например, ссылки на общий классификатор ОКЕИ напрямую вместо элемента подчиненного справочника) приведет к серьезным ошибкам. Программа не сможет рассчитать коэффициенты пересчета при оформлении документов (например, накладных), что приведет к нулевым суммам или ошибкам проведения.

Убедитесь, что вы связываете цену с корректным подчиненным элементом единицы. Рекомендуется использовать базовую или основную единицу товара:


СпрЦен.Единица = ТМЦ.БазоваяЕдиница; // или ТМЦ.ОсновнаяЕдиница (в зависимости от метаданных вашей базы)

Итоговый готовый шаблон кода для 1С 7.7

Сведем все лучшие практики, рассмотренные нами, в единый готовый программный модуль для загрузки цен из Excel:


Товар = СоздатьОбъект("Справочник.Номенклатура");
СпрЦен = СоздатьОбъект("Справочник.Цены");

Т1 = Прав((Строка(Excel.Cells(ТекСтрока, СтлбКод).Value)), 5);
Т2 = "0000" + Т1;
ХХ = Прав(Т2, 5);

Если Товар.НайтиПоКоду(ХХ, 0) = 1 Тогда
    ТМЦ = Товар.ТекущийЭлемент();
    ЦенаХЛ = Окр(Число(СокрЛП(Строка(Excel.Cells(ТекСтрока, СтлбЦена_Розн).Value))), 2, 1);
    
    Если ЦенаХЛ > 0 Тогда
        // Задаем владельца для ограничения области поиска
        СпрЦен.ИспользоватьВладельца(ТМЦ);
        
        // Переходим в режим работы с периодическими реквизитами на текущую дату
        СпрЦен.ИспользоватьДату(ТекущаяДата()); 
        
        // Быстро ищем существующую категорию цены у этого товара
        Если СпрЦен.НайтиПоРеквизиту("КатегорияЦены", Спис, 0) = 1 Тогда
            // Элемент найден и позиционирован. Обновляем периодическую цену
            СпрЦен.Цена = ЦенаХЛ;
            СпрЦен.Записать();
            Сообщить("Обновлена цена для товара: " + ТМЦ.Наименование);
        Иначе
            // Элемента нет, создаем новую карточку цены
            СпрЦен.Новый();
            СпрЦен.Валюта = Константа.БазоваяВалюта;
            
            // Заполняем правильную подчиненную единицу измерения товара
            СпрЦен.Единица = ТМЦ.БазоваяЕдиница; 
            СпрЦен.КатегорияЦены = Спис;
            СпрЦен.Цена = ЦенаХЛ;
            СпрЦен.Записать();
            Сообщить("Создана новая цена для товара: " + ТМЦ.Наименование);
        КонецЕсли;
    КонецЕсли;
Иначе
    Сообщить("Товар с кодом " + ХХ + " не найден в базе данных!", "!");
КонецЕсли;

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

← На главную