При разработке механизмов учета в системе 1С:Предприятие программисты часто сталкиваются с классической дилеммой: как обеспечить целостность данных в периодических регистрах сведений, если пользователи могут вводить документы «задним числом». Типовая проверка через СрезПоследних на дату документа отлично работает в хронологическом порядке, но мгновенно «ломается», когда в систему вносится документ с более ранней датой, чем уже существующие записи. Рассмотрим подробнее, почему это происходит и какие архитектурные подходы помогут решить эту задачу.
Представим ситуацию: у нас есть объект аренды. Документ №1 от 05.12.2020 фиксирует, что объект сдан. Если мы создаем Документ №2 от 04.12.2020, система при проверке сделает СрезПоследних на 04.12.2020. На этот момент объект еще свободен, и документ успешно проводится. В итоге в регистре появляются две записи, которые логически противоречат друг другу или создают «разрыв» в истории, который не учитывался при первичном проведении. Проанализируем основные методы решения этой проблемы.
Один из самых надежных способов, упомянутых экспертами, — это использование двух независимых регистров. Разберем этот подход пошагово:
ТекущееСостояниеОбъектов. В нем измерениями будут выступать сами объекты, а ресурсом — их текущий статус (например, «Сдан», «Свободен», «На ремонте»).ИсторияСостоянийОбъектов для построения отчетов на любую дату.При проведении документа мы всегда проверяем и обновляем именно непериодический регистр. Поскольку в нем нет привязки к дате, любая попытка сдать объект, который уже помечен как «Сдан» (неважно, будущим или прошлым числом), будет пресечена логикой контроля. Выясним, как это выглядит в коде:
// Пример проверки в модуле проведения документа
Блокировка = Новый БлокировкаДанных;
ЭлементБлокировки = Блокировка.Добавить("РегистрСведений.ТекущееСостояниеОбъектов");
ЭлементБлокировки.УстановитьЗначение("Объект", СсылкаНаОбъект);
Блокировка.Заблокировать();
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ
| ТекущееСостояние.Статус КАК Статус
|ИЗ
| РегистрСведений.ТекущееСостояниеОбъектов КАК ТекущееСостояние
|ГДЕ
| ТекущееСостояние.Объект = &Объект";
Запрос.УстановитьПараметр("Объект", СсылкаНаОбъект);
Результат = Запрос.Выполнить();
// Если статус уже "Сдан", отменяем проведение
Если Не Результат.Пустой() Тогда
Выборка = Результат.Выбрать();
Выборка.Следующий();
Если Выборка.Статус = Перечисления.СтатусыОбъектов.Сдан Тогда
Отказ = Истина;
Сообщение = Новый СообщениеПользователю;
Сообщение.Текст = "Объект уже сдан в аренду!";
Сообщение.Сообщить();
КонецЕсли;
КонецЕсли;
Если архитектура требует использования только одного периодического регистра, нам необходимо изменить логику запроса при проведении. Вместо получения СрезПоследних на дату документа, мы должны проверить наличие любых записей после даты текущего документа. Проанализируем ситуацию: если мы проводим документ от 04.12, нам нужно убедиться, что 05.12, 06.12 и так далее объект не занят.
Для этого в запросе устанавливаем условие Период >= &ДатаДокумента. Если запрос возвращает хотя бы одну запись — значит, хронология нарушена. Посмотрим на пример реализации такого контроля:
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ ПЕРВЫЕ 1
| Состояния.Период КАК ДатаДвижения,
| Состояния.Регистратор КАК ДокументКонфликта
|ИЗ
| РегистрСведений.СостоянияОбъектов КАК Состояния
|ГДЕ
| Состояния.Объект = &Объект
| И Состояния.Период >= &ДатаДокумента
| И Состояния.Регистратор <> &Ссылка";
Запрос.УстановитьПараметр("Объект", ТекущийОбъект);
Запрос.УстановитьПараметр("ДатаДокумента", Дата);
Запрос.УстановитьПараметр("Ссылка", Ссылка);
Результат = Запрос.Выполнить();
Если Не Результат.Пустой() Тогда
Выборка = Результат.Выбрать();
Выборка.Следующий();
// Логика запрета: после текущей даты уже есть движения по объекту
Отказ = Истина;
ТекстОшибки = СтрШаблон("Нельзя провести документ задним числом. Найден более поздний документ: %1 от %2",
Выборка.ДокументКонфликта, Выборка.ДатаДвижения);
ОбщегоНазначенияКлиентСервер.СообщитьПользователю(ТекстОшибки);
КонецЕсли;
Важно понимать, что никакой контроль данных не будет работать корректно в многопользовательской среде без управляемых блокировок. Если два пользователя одновременно проводят документы по одному и тому же объекту, они оба могут пройти проверку «Среза последних», так как данные еще не записаны в базу. Рассмотрим, как правильно применять БлокировкаДанных.
Блокировку следует устанавливать в самом начале процедуры ОбработкаПроведения. Мы блокируем пространство измерений регистра сведений по конкретному объекту. Это гарантирует, что второй процесс будет ждать завершения транзакции первого и увидит уже актуальные данные при выполнении своего запроса.
Рассмотрим интересный метод, предложенный участниками обсуждения: использование числового ресурса «Статус», значение которого всегда должно расти. Например:
При проведении документа система проверяет максимальный статус в регистре по данному объекту (используя агрегатную функцию МАКСИМУМ в запросе без ограничения по дате). Если текущий документ пытается установить статус, который ниже или равен существующему максимальному, — это признак нарушения логической цепочки или попытка ввода данных задним числом, которая изменит текущее (самое позднее) состояние объекта.
В задачах аренды часто эффективнее использовать не просто периодический регистр сведений, а регистр с двумя ресурсами: ДатаНачала и ДатаОкончания. В этом случае проверка при проведении превращается в поиск пересечения интервалов. Разберем логику: новый документ с периодом [Т1, Т2] не может быть проведен, если в базе уже есть запись с периодом [Д1, Д2], где:
(Т1 <= Д2) И (Т2 >= Д1)
Такой подход полностью исключает наложение документов друг на друга, независимо от того, в какой последовательности они вводятся в систему. Это наиболее жесткий и точный метод контроля для систем бронирования и аренды.
Подводя итог, выбор метода зависит от бизнес-требований. Если важна простота и скорость — используйте контроль «будущих» записей в запросе. Если важна максимальная надежность — внедряйте непериодический регистр текущих состояний в связке с управляемыми блокировками.