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