Как получить цену и наценку для склада и номенклатуры на встроенном языке 1С?

Программист 1С v8.3 (Управляемые формы) IT и автоматизация бизнеса
← На главную

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

Анализ текущей структуры хранения данных

Проанализируем ситуацию: разработчик создал табличную часть внутри справочника Номенклатура, в которой хранит две колонки: Склад и Наценка. При изменении строки товаров в документе, система должна найти нужную номенклатуру, посмотреть в её табличную часть, найти строку с соответствующим складом и вернуть процент наценки.

Чтобы получить эти данные, можно обратиться к реквизитам через точку (например, используя метод Найти() для табличной части), но в реалиях клиент-серверной архитектуры 1С это считается плохой практикой. Такое обращение вызывает неявное получение всего объекта базы данных, что сильно снижает производительность. Посмотрим на пример того, как правильно вытащить данные из табличной части справочника с помощью запроса.

Реализуем серверную функцию, которая будет принимать идентификатор строки документа и возвращать найденную наценку:


&НаСервере
Функция НаценкаДляСклада(Идентификатор)
    Наценка = Константы.НаценкаПоУмолчанию.Получить();
    
    // Находим текущую строку по идентификатору
    СтрокаТоваров = Объект.Товары.НайтиПоИдентификатору(Идентификатор);
    
    Если ЗначениеЗаполнено(СтрокаТоваров.Номенклатура) И ЗначениеЗаполнено(Объект.Склад) Тогда
        
        Запрос = Новый Запрос;
        Запрос.Текст = 
            "ВЫБРАТЬ
            |    НоменклатураНаценки.Наценка КАК Наценка
            |ИЗ
            |    Справочник.Номенклатура.НаценкиСкладов КАК НоменклатураНаценки
            |ГДЕ
            |    НоменклатураНаценки.Ссылка = &Номенклатура
            |    И НоменклатураНаценки.Склад = &Склад";
            
        // Устанавливаем параметры запроса из данных документа
        Запрос.УстановитьПараметр("Номенклатура", СтрокаТоваров.Номенклатура);
        Запрос.УстановитьПараметр("Склад", Объект.Склад); 
        
        Результат = Запрос.Выполнить();
        
        Если Не Результат.Пустой() Тогда
            Выборка = Результат.Выбрать();
            Выборка.Следующий();
            Наценка = Выборка.Наценка;
        КонецЕсли;
        
    КонецЕсли;
    
    Возврат Наценка;
КонецФункции

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

Архитектурные ошибки: почему нельзя хранить цены в справочниках

В платформе 1С справочники предназначены для хранения условно-постоянной нормативно-справочной информации (мастер-данных). Хранение динамических показателей, таких как цены или наценки, в табличных частях справочника приводит к ряду серьезных проблем. Разберем их подробнее:

  1. Отсутствие истории (периодичности). Табличная часть справочника хранит только одно текущее состояние. Если наценка изменится завтра, старое значение будет перезаписано и утеряно навсегда. При перепроведении старого документа реализации за прошлый месяц, система возьмет новую наценку, и финансовые итоги прошлого периода необратимо исказятся.
  2. Блокировки объектов базы данных. Чтобы изменить наценку хотя бы для одного склада, менеджеру или обработке придется целиком перезаписать элемент справочника Номенклатура. В этот момент справочник блокируется системой, и другие пользователи не смогут параллельно редактировать этот товар.
  3. Разрастание объема данных и падение производительности. Добавление табличных частей утяжеляет таблицу базы данных. При частом обращении к номенклатуре в списках и документах платформа будет потреблять больше оперативной памяти.

Как правильно спроектировать систему ценообразования

Если мы посмотрим на типовые конфигурации (Управление торговлей, ERP, Управление нашей фирмой), то увидим стандартизированную подсистему ценообразования. Давайте спроектируем ее правильно. Нам потребуется:

  1. Создать независимый справочник ВидыЦен. В нем будут элементы: "Оптовая", "Розничная" и т.д.
  2. В справочник Склады добавить реквизит ВидЦены. Таким образом, розничный склад будет связан с розничным видом цены, а оптовый — с оптовым.
  3. Создать периодический РегистрСведений с названием ЦеныНоменклатуры. Измерениями будут выступать Номенклатура и ВидЦены, а ресурсом — Цена (или Наценка). Периодичность регистра (например, в пределах дня или секунды) позволит сохранять историю изменений.
  4. Вносить изменения в регистр не вручную, а с помощью документа-регистратора УстановкаЦенНоменклатуры. Это позволит фиксировать, кто и когда изменил прайс-лист.

Пример получения наценки из регистра сведений

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


&НаСервере
Функция ПолучитьЦенуПоРегистру(Идентификатор)
    Цена = 0;
    СтрокаТоваров = Объект.Товары.НайтиПоИдентификатору(Идентификатор);
    
    Если ЗначениеЗаполнено(СтрокаТоваров.Номенклатура) И ЗначениеЗаполнено(Объект.Склад) Тогда
        
        Запрос = Новый Запрос;
        Запрос.Текст = 
            "ВЫБРАТЬ
            |    ЦеныНоменклатурыСрезПоследних.Цена КАК Цена
            |ИЗ
            |    РегистрСведений.ЦеныНоменклатуры.СрезПоследних(
            |            &ДатаДокумента,
            |            Номенклатура = &Номенклатура
            |                И ВидЦены = &ВидЦены) КАК ЦеныНоменклатурыСрезПоследних";

        // Важно: передаем дату документа, чтобы получить цену актуальную на момент создания документа
        Запрос.УстановитьПараметр("ДатаДокумента", Объект.Дата);
        Запрос.УстановитьПараметр("Номенклатура", СтрокаТоваров.Номенклатура);
        // Вид цены берем из реквизита выбранного склада
        Запрос.УстановитьПараметр("ВидЦены", Объект.Склад.ВидЦены);
        
        Результат = Запрос.Выполнить();
        
        Если Не Результат.Пустой() Тогда
            Выборка = Результат.Выбрать();
            Выборка.Следующий();
            Цена = Выборка.Цена;
        КонецЕсли;
        
    КонецЕсли;
    
    Возврат Цена;
КонецФункции

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


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

// Метод возвращает структуру ресурсов регистра актуальных на переданную дату
РесурсыРегистра = РегистрыСведений.ЦеныНоменклатуры.ПолучитьПоследнее(Объект.Дата, Отбор);

Если РесурсыРегистра <> Неопределено Тогда
    ТекущаяЦена = РесурсыРегистра.Цена;
КонецЕсли;

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

← На главную