При разработке механизмов контроля цен в системе 1С:Предприятие программисты часто сталкиваются с ситуацией, когда виртуальная таблица СрезПоследних ведет себя «нелогично». Типичный сценарий: мы пытаемся внедрить проверку, которая не позволит пользователю установить слишком низкую или слишком высокую цену по сравнению с предыдущим значением. Однако при попытке получить «текущую» (последнюю) цену из базы данных, система возвращает вчерашнее значение, игнорируя то, которое вводится прямо сейчас. Рассмотрим подробнее, почему так происходит и как правильно организовать получение данных в таких случаях.
Разберем ситуацию на примере кода, написанного в модуле набора записей регистра сведений ЦеныНоменклатуры в обработчике события ПередЗаписью. Проблема заключается в фундаментальном принципе работы СУБД и платформы 1С: данные, которые передаются в систему в текущий момент, еще не записаны в физические таблицы базы данных.
Когда мы выполняем запрос к виртуальной таблице СрезПоследних внутри метода ПередЗаписью, происходит следующее:
ЭтотОбъект (набор записей) уже лежат новые данные (например, цена 200).Именно поэтому, даже если вы установите параметр &ТекДата в значение КонецДня(ТекущаяДата()), запрос вернет результат «вчерашним числом» — это последняя точка, зафиксированная в базе на момент обращения.
Важным фактором является настройка периодичности регистра. Если для регистра ЦеныНоменклатуры установлена периодичность В пределах дня, то в одну дату для одного набора измерений (Номенклатура, ВидЦен, Характеристика) может существовать только одна запись.
Проанализируем логику замещения записей: если мы сегодня уже записывали цену (например, утром была установлена цена 150), а днем решили изменить её на 200, то при записи второй цены первая будет физически удалена или перезаписана. В момент выполнения ПередЗаписью старая «сегодняшняя» запись может быть уже помечена системой на удаление из индекса, и запрос СрезПоследних на текущую дату может вернуть либо пустое значение, либо запись за предыдущий день. Это создает иллюзию того, что данные «зависли» или используется какой-то кэш.
В среде 1С нет официального понятия «кэшевый регистр», однако пользователи или заказчики могут так называть регистры, у которых включена опция «Разрешить итоги среза последних». Рассмотрим, на что это влияет:
Если этот флаг установлен, платформа создает отдельную таблицу в БД для хранения актуального среза. Это ускоряет получение цен, когда запрос идет без параметра «Период». Если данные возвращаются некорректно, можно попробовать пересчитать итоги регистров сведений через консоль обслуживания или программно:
РегистрыСведений.ЦеныНоменклатуры.УстановитьИспользованиеИтогов(Истина);
Однако в 99% случаев проблема кроется не в итогах, а в логике самого запроса внутри транзакции записи.
Чтобы сравнить новую цену со старой и реализовать контроль (например, запрет изменения более чем на 50%), нам нужно получить действительно последнюю зафиксированную цену, которая была в базе до начала текущей записи.
Рассмотрим по шагам, как это сделать правильно:
ПередЗаписью мы не должны полагаться на СрезПоследних с текущей датой.Пример правильного подхода к получению предыдущей цены в коде:
// В обработчике ПередЗаписью модуля набора записей
Для Каждого Запись Из ЭтотОбъект Цикл
// Определяем момент времени для получения старой цены.
// Если периодичность - день, то берем начало текущего дня минус 1 секунда,
// чтобы гарантированно не попасть в текущую записываемую запись.
ДатаДляСреза = НачалоДня(Запись.Период) - 1;
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ
| ЦеныСрез.Цена КАК СтараяЦена
|ИЗ
| РегистрСведений.ЦеныНоменклатуры.СрезПоследних(&ПериодСреза, Номенклатура = &Номенклатура И ВидЦен = &ВидЦен) КАК ЦеныСрез";
Запрос.УстановитьПараметр("ПериодСреза", ДатаДляСреза);
Запрос.УстановитьПараметр("Номенклатура", Запись.Номенклатура);
Запрос.УстановитьПараметр("ВидЦен", Запись.ВидЦен);
РезультатЗапроса = Запрос.Выполнить();
// Далее логика сравнения Запись.Цена и СтараяЦена
КонецЦикла;
Проанализируем ситуацию, когда записей много. Выполнять запрос в цикле — это «плохой тон» в 1С. Правильнее будет сначала собрать все номенклатурные позиции из набора записей ЭтотОбъект, а затем одним запросом получить все старые цены во временную таблицу.
Рассмотрим пример оптимизированного кода:
// 1. Собираем уникальные пары Номенклатура-ВидЦен из набора
ТаблицаНовыхДанных = ЭтотОбъект.Выгрузить();
ТаблицаНовыхДанных.Свернуть("Номенклатура, ВидЦен");
// 2. Передаем их в запрос для получения среза цен на момент "до сегодня"
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ
| НовыеЦены.Номенклатура,
| НовыеЦены.ВидЦен
|ПОМЕСТИТЬ ВТ_Параметры
|ИЗ
| &ТаблицаНовыхДанных КАК НовыеЦены
|;
|ВЫБРАТЬ
| Срез.Номенклатура,
| Срез.ВидЦен,
| Срез.Цена КАК СтараяЦена
|ИЗ
| РегистрСведений.ЦеныНоменклатуры.СрезПоследних(&ДатаКонтроля,
| (Номенклатура, ВидЦен) В (ВЫБРАТЬ Номенклатура, ВидЦен ИЗ ВТ_Параметры)) КАК Срез";
Запрос.УстановитьПараметр("ТаблицаНовыхДанных", ТаблицаНовыхДанных);
// Используем начало дня, если записи всегда идут "сегодня"
Запрос.УстановитьПараметр("ДатаКонтроля", НачалоДня(ТекущаяДата()));
Результат = Запрос.Выполнить().Выгрузить();
// Теперь можно сопоставить данные и выполнить проверку отклонения
Важный нюанс: Если ваш регистр использует измерения Характеристика или Партия, обязательно включайте их в условия запроса и в СрезПоследних. Часто причиной «неверных» цен является то, что программист забывает про пустые характеристики, которые в базе могут храниться как Справочники.ХарактеристикиНоменклатуры.ПустаяСсылка(), а в запросе не учитываться, из-за чего срез выдает первую попавшуюся запись.
Если вы работаете в 1С:Управление нашей фирмой, проанализируйте общий модуль УправлениеНебольшойФирмойСервер. Там существует функция ПолучитьЦенуНоменклатурыПоВидуЦен(). Использование типовых функций гарантирует, что вы учтете все нюансы (валюты, единицы измерения, характеристики), которые уже заложены разработчиками фирмы «1С».
Выясним причину, почему это лучше: типовые функции часто используют кэширование на уровне сеанса (через параметры сеанса или модули с повторным использованием). Это значительно ускоряет работу, но требует осторожности при записи: если вы изменили цену, а потом в этой же транзакции вызываете типовую функцию получения цены, она может вернуть старое значение из своего внутреннего кэша.
Подводя итог, при работе с регистром ЦеныНоменклатуры всегда четко разделяйте данные, которые уже зафиксированы в базе, и данные, которые находятся в памяти объекта в момент записи. Для контроля изменений всегда делайте срез на дату, предшествующую дате новой записи.