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

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

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

Проанализируем ситуацию: у нас есть регистр сведений для хранения цен. В его структуре есть измерения Товар и Адрес (магазин), а в ресурсах — Цена, а также поля ДатаНачалаДействияЦены и ДатаОкончанияДействияЦены. Задача — при выборе товара в документе подставить цену, действующую на дату этого документа. Попытка получить цену запросом через виртуальную таблицу СрезПоследних не дает ожидаемого результата. Выясним причину и найдем правильное решение.

В чем заключается основная ошибка?

Ключевая проблема заключается в неверном понимании механизма периодичности в 1С и, как следствие, в неправильном проектировании структуры регистра сведений. Автор исходного кода пытается использовать виртуальную таблицу СрезПоследних, которая предназначена исключительно для периодических регистров сведений.

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

  1. Пересечение интервалов. Ничто не мешает пользователю создать две записи для одного и того же товара в одном магазине с пересекающимися периодами действия. Например:
    • Товар 1, Магазин 1, Цена 100 руб., действует с 01.05 по 10.05.
    • Товар 1, Магазин 1, Цена 200 руб., действует с 05.05 по 15.05.
    Какую цену должен вернуть запрос на дату 07.05? Система не сможет однозначно ответить на этот вопрос, что приведет к ошибкам в учете.
  2. Низкая производительность. Чтобы найти нужную цену, придется сканировать всю таблицу регистра и для каждой записи проверять, попадает ли дата документа в интервал. На больших объемах данных это будет работать очень медленно.
  3. Усложнение запросов. Код для получения данных становится сложнее и менее очевидным, чем при использовании стандартных механизмов платформы.

Теперь рассмотрим, как исправить эту ситуацию.

Решение 1 (Правильное и рекомендуемое): Перепроектирование регистра

Самый надежный и соответствующий стандартам 1С путь — это использовать встроенный механизм периодичности. Для этого необходимо изменить структуру регистра сведений в конфигураторе.

Разберем по шагам, что нужно сделать:

  1. Установить периодичность регистра. Откройте в конфигураторе ваш регистр сведений "ЦеныНаТоварыВМагазинах". В палитре свойств найдите свойство "Периодичность" и установите его в значение, соответствующее вашей задаче. Чаще всего для цен используют "По позиции регистратора" (если цены устанавливаются документом) или "В пределах дня".

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

  2. Удалить лишние ресурсы. Поля ДатаНачалаДействияЦены и ДатаОкончанияДействияЦены теперь не нужны. Смело удаляйте их из состава ресурсов регистра. Дата начала действия теперь хранится в поле Период, а дата окончания неявно определяется датой установки следующей цены для этого же набора измерений (Товар, Адрес).

  3. Использовать документ-регистратор. Рекомендуется сделать регистр подчиненным регистратору. Создайте документ, например, "Установка цен номенклатуры". При проведении этого документа будут формироваться записи в нашем регистре цен. Дата документа автоматически будет записываться в поле Период регистра.

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

Посмотрим на пример обновленного запроса:


&НаСервереБезКонтекста
Функция ПолучитьЦенуНаДату(Товар, Дата, Адрес)
    Результат = 0; // Возвращаем просто число, а не структуру

    Запрос = Новый Запрос;
    Запрос.Текст =
        "ВЫБРАТЬ ПЕРВЫЕ 1
        |    ЦеныСрезПоследних.Цена КАК Цена
        |ИЗ
        |    РегистрСведений.ЦеныНаТоварыВМагазинах.СрезПоследних(&Дата, Товар = &Товар И Адрес = &Адрес) КАК ЦеныСрезПоследних";

    Запрос.УстановитьПараметр("Адрес", Адрес);
    Запрос.УстановитьПараметр("Дата", Дата);
    Запрос.УстановитьПараметр("Товар", Товар);

    РезультатЗапроса = Запрос.Выполнить();

    Если НЕ РезультатЗапроса.Пустой() Тогда
        Выборка = РезультатЗапроса.Выбрать();
        Выборка.Следующий();
        Результат = Выборка.Цена;
    КонецЕсли;

    Возврат Результат;
КонецФункции

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

Решение 2 (Обходное, нерекомендуемое): Адаптация запроса под неверную структуру

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

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

Посмотрим на пример такого запроса:


&НаСервереБезКонтекста
Функция ПолучитьЦенуИзИнтервала(Товар, Дата, Адрес)
    Результат = 0;

    Запрос = Новый Запрос;
    Запрос.Текст =
        "ВЫБРАТЬ ПЕРВЫЕ 1
        |    Цены.Цена КАК Цена
        |ИЗ
        |    РегистрСведений.ЦеныНаТоварыВМагазинах КАК Цены
        |ГДЕ
        |    Цены.Товар = &Товар
        |    И Цены.Адрес = &Адрес
        |    И &Дата МЕЖДУ Цены.ДатаНачалаДействияЦены И Цены.ДатаОкончанияДействияЦены";

    Запрос.УстановитьПараметр("Адрес", Адрес);
    Запрос.УстановитьПараметр("Дата", Дата);
    Запрос.УстановитьПараметр("Товар", Товар);

    РезультатЗапроса = Запрос.Выполнить();

    Если НЕ РезультатЗапроса.Пустой() Тогда
        Выборка = РезультатЗапроса.Выбрать();
        Выборка.Следующий();
        Результат = Выборка.Цена;
    КонецЕсли;

    Возврат Результат;
КонецФункции

Важно понимать недостатки этого подхода:

Этот способ следует использовать только как временное решение или в крайнем случае, когда модификация конфигурации невозможна.

Дополнительные полезные советы

  1. Правильно именуйте объекты. В контексте задачи справочник Адреса лучше назвать Магазины или ТорговыеТочки. Это делает конфигурацию более понятной для других разработчиков.
  2. Используйте виды цен. В реальных системах почти всегда требуется хранить несколько типов цен (закупочная, оптовая, розничная, акционная). Для этого в регистр сведений добавляют еще одно измерение — ВидЦены (ссылка на одноименный справочник).
  3. Изучайте типовые конфигурации. Лучший способ научиться правильно проектировать — это посмотреть, как подобные задачи решены в стандартных конфигурациях от 1С, таких как "Управление торговлей" или "ERP". Механизмы ценообразования в них продуманы и отлажены годами практики.

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

← На главную