При разработке учетных систем часто возникает задача хранения истории цен и получения актуальной цены на определенную дату, например, на дату проведения документа продажи. Новички в 1С нередко совершают ошибку при проектировании регистра сведений для этих целей, что приводит к неработающему коду и неверным данным. Разберем типичную проблему и правильные способы ее решения.
Проанализируем ситуацию: у нас есть регистр сведений для хранения цен. В его структуре есть измерения Товар и Адрес (магазин), а в ресурсах — Цена, а также поля ДатаНачалаДействияЦены и ДатаОкончанияДействияЦены. Задача — при выборе товара в документе подставить цену, действующую на дату этого документа. Попытка получить цену запросом через виртуальную таблицу СрезПоследних не дает ожидаемого результата. Выясним причину и найдем правильное решение.
Ключевая проблема заключается в неверном понимании механизма периодичности в 1С и, как следствие, в неправильном проектировании структуры регистра сведений. Автор исходного кода пытается использовать виртуальную таблицу СрезПоследних, которая предназначена исключительно для периодических регистров сведений.
В нашем же случае регистр не является периодическим. Вместо использования встроенного механизма платформы, разработчик попытался реализовать периодичность самостоятельно, добавив в ресурсы поля ДатаНачалаДействияЦены и ДатаОкончанияДействияЦены. Такой подход не только делает невозможным использование удобных виртуальных таблиц (СрезПоследних, СрезПервых), но и порождает ряд других серьезных проблем:
Теперь рассмотрим, как исправить эту ситуацию.
Самый надежный и соответствующий стандартам 1С путь — это использовать встроенный механизм периодичности. Для этого необходимо изменить структуру регистра сведений в конфигураторе.
Разберем по шагам, что нужно сделать:
Установить периодичность регистра. Откройте в конфигураторе ваш регистр сведений "ЦеныНаТоварыВМагазинах". В палитре свойств найдите свойство "Периодичность" и установите его в значение, соответствующее вашей задаче. Чаще всего для цен используют "По позиции регистратора" (если цены устанавливаются документом) или "В пределах дня".
После этого платформа автоматически добавит в регистр системное поле Период. Именно это поле теперь будет хранить дату начала действия цены.
Удалить лишние ресурсы. Поля ДатаНачалаДействияЦены и ДатаОкончанияДействияЦены теперь не нужны. Смело удаляйте их из состава ресурсов регистра. Дата начала действия теперь хранится в поле Период, а дата окончания неявно определяется датой установки следующей цены для этого же набора измерений (Товар, Адрес).
Использовать документ-регистратор. Рекомендуется сделать регистр подчиненным регистратору. Создайте документ, например, "Установка цен номенклатуры". При проведении этого документа будут формироваться записи в нашем регистре цен. Дата документа автоматически будет записываться в поле Период регистра.
После этих изменений структура регистра станет канонической, и ваш первоначальный запрос с использованием виртуальной таблицы СрезПоследних станет абсолютно корректным и будет работать быстро и правильно.
Посмотрим на пример обновленного запроса:
&НаСервереБезКонтекста
Функция ПолучитьЦенуНаДату(Товар, Дата, Адрес)
Результат = 0; // Возвращаем просто число, а не структуру
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ ПЕРВЫЕ 1
| ЦеныСрезПоследних.Цена КАК Цена
|ИЗ
| РегистрСведений.ЦеныНаТоварыВМагазинах.СрезПоследних(&Дата, Товар = &Товар И Адрес = &Адрес) КАК ЦеныСрезПоследних";
Запрос.УстановитьПараметр("Адрес", Адрес);
Запрос.УстановитьПараметр("Дата", Дата);
Запрос.УстановитьПараметр("Товар", Товар);
РезультатЗапроса = Запрос.Выполнить();
Если НЕ РезультатЗапроса.Пустой() Тогда
Выборка = РезультатЗапроса.Выбрать();
Выборка.Следующий();
Результат = Выборка.Цена;
КонецЕсли;
Возврат Результат;
КонецФункции
Этот запрос эффективно получит единственную актуальную цену на указанную дату, используя оптимизированный механизм платформы.
Представим, что по каким-то причинам вы не можете изменить структуру регистра. В этом случае придется отказаться от СрезПоследних и написать запрос, который будет вручную анализировать ваши поля с датами.
Логика запроса будет следующей: нужно выбрать из основной таблицы регистра все записи, удовлетворяющие условиям по товару и магазину, и где дата документа находится между датой начала и датой окончания действия цены.
Посмотрим на пример такого запроса:
&НаСервереБезКонтекста
Функция ПолучитьЦенуИзИнтервала(Товар, Дата, Адрес)
Результат = 0;
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ ПЕРВЫЕ 1
| Цены.Цена КАК Цена
|ИЗ
| РегистрСведений.ЦеныНаТоварыВМагазинах КАК Цены
|ГДЕ
| Цены.Товар = &Товар
| И Цены.Адрес = &Адрес
| И &Дата МЕЖДУ Цены.ДатаНачалаДействияЦены И Цены.ДатаОкончанияДействияЦены";
Запрос.УстановитьПараметр("Адрес", Адрес);
Запрос.УстановитьПараметр("Дата", Дата);
Запрос.УстановитьПараметр("Товар", Товар);
РезультатЗапроса = Запрос.Выполнить();
Если НЕ РезультатЗапроса.Пустой() Тогда
Выборка = РезультатЗапроса.Выбрать();
Выборка.Следующий();
Результат = Выборка.Цена;
КонецЕсли;
Возврат Результат;
КонецФункции
Важно понимать недостатки этого подхода:
ВЫБРАТЬ ПЕРВЫЕ 1 вернет случайную из них (без УПОРЯДОЧИТЬ ПО), что является потенциальной ошибкой.СрезомПоследних, так как СУБД придется выполнять полное сканирование с фильтрацией по неиндексируемому диапазону.Этот способ следует использовать только как временное решение или в крайнем случае, когда модификация конфигурации невозможна.
Адреса лучше назвать Магазины или ТорговыеТочки. Это делает конфигурацию более понятной для других разработчиков.ВидЦены (ссылка на одноименный справочник).В заключение, всегда старайтесь использовать стандартные механизмы платформы 1С. Они не только упрощают разработку и повышают производительность, но и делают вашу систему более надежной и предсказуемой.