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

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

В процессе разработки конфигураций, особенно в таких специфических сферах, как гостиничный бизнес, часто возникает задача жесткого контроля данных. Одной из самых распространенных проблем является «овербукинг» — ситуация, когда на одну и ту же койку или номер в один и тот же период времени пытаются заселить нескольких человек. Разберем детально, как реализовать надежный механизм запрета записи документа, если данные пересекаются с уже существующими записями в системе.

Базовое решение: Использование события ПередЗаписью

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

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


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

	Запрос.УстановитьПараметр("Номер", НомерКомнаты);
	Запрос.УстановитьПараметр("Койка", НомерКойки);
	Запрос.УстановитьПараметр("ДатаНачала", ДатаНачала);
	Запрос.УстановитьПараметр("ДатаОкончания", ДатаОкончания);
	Запрос.УстановитьПараметр("Ссылка", Ссылка);

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

	Если Не РезультатЗапроса.Пустой() Тогда
		Сообщение = Новый СообщениеПользователю;
		Сообщение.Текст = "Внимание! Комната на этот период уже занята!";
		Сообщение.Сообщить();
		Отказ = Истина;
	КонецЕсли;

КонецПроцедуры

В этом коде ключевым является параметр Отказ = Истина. Установка этого значения в обработчике события полностью отменяет запись объекта и завершает транзакцию.

Нюанс с ОбменДанными.Загрузка: Почему проверка может не сработать

Часто программисты сталкиваются с тем, что проверка «обходится» при работе внешних обработок или при обмене данными. Выясним причину этого явления. В типовых решениях (и по правилам хорошего тона) в начале обработчика ПередЗаписью ставят условие:


Если ОбменДанными.Загрузка Тогда
    Возврат;
КонецЕсли;

Если ваша задача — гарантировать уникальность данных на 100%, даже при загрузке из внешних источников, проверку пересечения периодов нужно выносить выше этого блока. Однако будьте осторожны: это может замедлить массовую загрузку данных или вызвать ошибки при переносе старой базы с уже имеющимися конфликтами.

Использование управляемых блокировок для исключения «гонки данных»

Рассмотрим более сложную ситуацию: два администратора гостиницы одновременно открывают новые документы «Заселение» для одного и того же номера. В момент нажатия кнопки «Записать» запрос в процедуре ПередЗаписью у обоих покажет, что номер свободен, так как записи в регистре еще нет. В итоге в базе появятся два конфликтующих документа.

Чтобы этого избежать, проанализируем использование Управляемых блокировок. Мы должны заблокировать ресурс (комнату и интервал дат) в самом начале транзакции записи. Посмотрим на пример реализации:


Блокировка = Новый БлокировкаДанных;
ЭлементБлокировки = Блокировка.Добавить("РегистрСведений.ЗанятостьНомеров");
ЭлементБлокировки.УстановитьПараметр("Номер", НомерКомнаты);
ЭлементБлокировки.Режим = РежимБлокировкиДанных.Исключительный;
// Блокировка накладывается по измерениям регистра
Блокировка.Заблокировать();

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

Метод «ОбработкаПроверкиЗаполнения» для интерактивной работы

Если мы хотим не просто «отфутболить» пользователя ошибкой записи, а сделать интерфейс дружелюбным, разберем использование метода ОбработкаПроверкиЗаполнения. Этот метод вызывается платформой при нажатии кнопок записи или проведения и позволяет визуально выделить проблемные поля (например, даты).

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

Контроль на уровне регистра сведений

Посмотрим на ситуацию со стороны архитектуры базы данных. Самый надежный «последний рубеж» — это сам регистр. Если сделать измерения регистра таким образом, чтобы они составляли уникальный ключ (например, Номер + Дата), платформа 1С просто не позволит записать дубль на уровне СУБД, выдав ошибку «Запись с такими ключевыми полями существует».

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

Подведем итоги

Для реализации «пуленепробиваемого» запрета записи выполним следующие шаги:

  1. В модуле объекта используем ПередЗаписью.
  2. Внутри обработчика обязательно накладываем Исключительную блокировку данных по ключевым измерениям (номер, койка).
  3. Выполняем запрос к регистру для поиска пересечений периодов, исключая текущий документ (сравнение по ссылке).
  4. Если пересечение найдено — устанавливаем Отказ = Истина и выводим сообщение пользователю.
  5. Для защиты от групповых обработок учитываем состояние флага ОбменДанными.Загрузка.

Именно сочетание этих методов позволяет создать стабильную систему, устойчивую как к ошибкам пользователей, так и к коллизиям при одновременной работе нескольких сотрудников.

← На главную