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