Как правильно проверять регистры при проведении документа?

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

Проблема контроля состояний и остатков при проведении документов «задним числом» — это классическая дилемма разработчика 1С. Основной конфликт заключается в том, что система должна быть одновременно гибкой (позволять исправлять ошибки прошлого) и строгой (не допускать нарушения логической цепочки событий). В данной статье мы подробно разберем, как реализовать надежную проверку регистров, учитывая специфику работы с осью времени.

Постановка проблемы и ограничения стандартного подхода

Рассмотрим ситуацию, описанную автором: существует регистр сведений СтатусыОсновныхСредств. Нам нужно гарантировать, что основное средство (ОС) принимается к учету только один раз или находится в статусе «Ожидается принятие». Если мы используем простой запрос к виртуальной таблице СрезПоследних на дату документа, мы сталкиваемся с двумя рисками:

  1. Риск «будущих» записей: Проводя документ январем, мы можем не увидеть, что в марте это ОС уже было списано.
  2. Риск блокировки перепроведения: Если мы проверяем статус вообще без привязки к дате, мы не сможем перепроводить старые документы, так как «будущие» документы уже изменили статус на «В работе».

Разберем основные стратегии решения этой задачи, которые применяются в профессиональной разработке.

Вариант 1. Разделение на оперативный и неоперативный контроль

В платформе 1С существует штатное разделение режимов проведения. Это позволяет гибко настраивать логику в зависимости от того, вводится ли документ «сейчас» или правится «прошлое».

Проанализируем пример кода в обработке проведения:


Процедура ОбработкаПроведения(Отказ, РежимПроведения)
    
    Если РежимПроведения = РежимПроведенияДокумента.Оперативный Тогда
        // Применяем максимально жесткие проверки текущего состояния
        ВыполнитьЖесткийКонтроль(Отказ);
    Иначе
        // При неоперативном проведении (задним числом) 
        // логика может быть смягчена или дополнена проверкой всей цепочки
        ВыполнитьКонтрольЦелостности(Отказ);
    КонецЕсли;

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

Оперативное проведение выполняется, когда дата документа совпадает с текущим моментом. Здесь мы можем доверять актуальным итогам. Неоперативное проведение требует более детального анализа: либо мы запрещаем изменения, влияющие на будущее, либо пересчитываем всю цепочку.

Вариант 2. Контроль «Будущих записей» через физическую таблицу

Чтобы избежать ситуации, когда январский документ «ломает» логику мартовского, мы должны проверить наличие любых записей в регистре после даты нашего документа. Для этого мы будем обращаться не к виртуальной таблице, а к основной (физической) таблице регистра.

Рассмотрим, как составить такой запрос:


Запрос = Новый Запрос;
Запрос.Текст = 
    "ВЫБРАТЬ ПЕРВЫЕ 1
    |   СтатусыОсновныхСредств.Период КАК ПериодБудущейЗаписи,
    |   СтатусыОсновныхСредств.Регистратор КАК Регистратор
    |ИЗ
    |   РегистрСведений.СтатусыОсновныхСредств КАК СтатусыОсновныхСредств
    |ГДЕ
    |   СтатусыОсновныхСредств.ОС = &ОС
    |   И СтатусыОсновныхСредств.Период > &ДатаТекущегоДокумента";

Запрос.УстановитьПараметр("ОС", ТекущееОС);
Запрос.УстановитьПараметр("ДатаТекущегоДокумента", МоментВремени());

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

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

Вариант 3. Фиксация состояния в реквизите справочника

Интересный подход был предложен в обсуждении: использовать справочник ОсновныеСредства как «якорь». Мы можем добавить в него реквизит ДокументПринятияКУчету.

Проанализируем логику этого метода:

  1. При проведении документа проверяем: если в справочнике ОС реквизит ДокументПринятияКУчету уже заполнен и это НЕ текущий документ — выдаем ошибку.
  2. Если реквизит пуст — записываем туда ссылку на текущий документ.
  3. При отмене проведения — очищаем реквизит (только если там записана ссылка на наш документ).

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

Вариант 4. Новая методика проведения (Запись — Проверка)

Современный стандарт разработки в 1С подразумевает, что документ сначала записывает свои движения, а затем проверяет, не привели ли эти движения к недопустимому состоянию. Это позволяет избежать проблем, когда документ должен «видеть сам себя».

Разберем алгоритм по шагам:

  1. Устанавливаем Движения.СтатусыОсновныхСредств.Записывать = Истина;.
  2. Записываем движения (пустые или заполненные — неважно, платформа сделает это при выходе из процедуры или при явном вызове Движения.Записать()).
  3. Выполняем запрос к СрезПоследних, используя в качестве границы Момент Времени текущего документа.

// Сначала записываем текущее состояние документа в регистр
Движения.СтатусыОсновныхСредств.Записать();

// Теперь проверяем, какое состояние получилось "на выходе"
ГраницаПроверки = Новый Граница(МоментВремени(), ВидГраницы.Включая);

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

Запрос.УстановитьПараметр("Граница", ГраницаПроверки);
Запрос.УстановитьПараметр("ОС", Объект.ОС);

// Анализируем результат и, если статус некорректен, ставим Отказ = Истина

Важность управляемых блокировок

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

Чтобы этого не произошло, необходимо использовать объект БлокировкаДанных:


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

Важно: Блокировку нужно устанавливать до выполнения запросов к регистру в транзакции проведения.

Итоги и рекомендации

Мы рассмотрели несколько подходов к контролю регистров. Выбор конкретного метода зависит от бизнес-задачи:

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

← На главную