Почему программно созданный документ в 1С записывается, но не появляется в базе данных?

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

В практике разработки на платформе 1С Предприятие нередко возникает загадочная ситуация: разработчик пишет код для программного создания документа (например, документа Событие в конфигурации 1С:Комплексная автоматизация 1.1), запускает его, система отрабатывает без каких-либо видимых ошибок или отказов. Документу даже присваивается очередной уникальный номер. Однако при попытке найти этот документ в списке или через запрос выясняется, что его физически нет в базе данных. При повторном выполнении кода система снова создает документ, присваивает ему тот же самый номер, но объект опять бесследно исчезает.

В этой статье мы подробно проанализируем причины такого поведения платформы 1С, разберем физику процесса взаимодействия с СУБД и рассмотрим готовые практические решения этой проблемы.

Симптомы проблемы и их анализ

Давайте систематизируем ключевые признаки, которые описывают эту техническую проблему:

  1. Программное создание выполняется успешно: Метод Записать() отрабатывает без вызова исключений. Свойство Отказ в сопутствующих обработчиках не выставляется в значение Истина.
  2. Дублирование номеров при повторном запуске: При первом запуске кодом создается, например, документ №000002. В списке его нет. При втором запуске система снова предлагает создать документ с номером №000002. Это верный признак того, что предыдущая запись была полностью стерта, и счетчик автонумерации откатился назад.
  3. Странное поведение Журнала регистрации (ЖР): В ЖР присутствует запись о транзакции создания документа с его представлением (например, "Событие №000002 от..."), но при попытке открыть документ из ЖР или посмотреть детальные данные система выдает ошибку: "Данные — Объект не найден".

Почему это происходит: механизм транзакций СУБД

Описанное поведение — это классическое проявление отката транзакции базы данных (Database Transaction Rollback). Давайте разберем этот механизм по шагам, чтобы понять логику работы платформы.

Когда вы вызываете метод Записать() для программно создаваемого объекта, это действие почти всегда происходит в контексте какой-то существующей транзакции. В типовых конфигурациях обычного приложения (таких как КА 1.1 или УПП 1.3) программный код создания подчиненных документов часто помещают внутрь модулей других объектов (например, в обработчики ПриЗаписи, ОбработкаПроведения родительского документа или в подписки на события).

Процесс протекает следующим образом:

  1. Платформа открывает общую транзакцию для записи родительского документа.
  2. Внутри этой транзакции запускается ваш код создания документа Событие.
  3. Вы вызываете метод Записать() для нового документа. СУБД временно записывает данные в таблицы базы данных, генерирует для объекта уникальный идентификатор (UUID) и выделяет очередной номер (например, №2).
  4. Важный нюанс: Платформа 1С сразу отправляет событие записи в Журнал регистрации. Дело в том, что Журнал регистрации в 1С не является транзакционным. Запись в него попадает в реальном времени, независимо от того, зафиксируется ли в итоге транзакция в базе данных SQL или нет. Поэтому в ЖР остается след от документа №2.
  5. После этого в родительской транзакции происходит сбой. Это может быть отказ проведения основного документа (например, не хватило остатков на складе, сработал контроль взаиморасчетов или программно было установлено Отказ = Истина в обработчике проведения). Также причиной может стать ошибка СУБД, "заглушенная" разработчиком внутри конструкции Попытка ... Исключение.
  6. Поскольку в рамках одной транзакции произошла ошибка, СУБД выполняет команду ROLLBACK (откат). Все изменения, внесенные в БД с момента начала транзакции, полностью аннулируются. Запись о вашем новом документе Событие физически удаляется из таблиц СУБД, а счетчик автонумерации возвращается в исходное состояние.
  7. В итоге: документа в базе нет, номер освободился для следующей попытки, а в ЖР осталась "битая" ссылка на несуществующий UUID ("Объект не найден").

Опасность скрытых исключений в транзакциях

Отдельно проанализируем ситуацию с блоками перехвата ошибок. Многие разработчики совершают критическую ошибку, заворачивая запись объектов в транзакции в конструкцию Попытка ... Исключение:


НачатьТранзакцию();
Попытка
    НовыйДокумент = Документы.Событие.СоздатьДокумент();
    // ... Заполнение реквизитов ...
    НовыйДокумент.Записать();
    
    // Вызов кода, который вызывает ошибку базы данных (например, дублирование уникального индекса)
    ВыполнитьНекорректнуюОперациюБД(); 
    
    ЗафиксироватьТранзакцию();
Исключение
    // Ошибка перехвачена, транзакция НЕ отменена явно в коде
    ОтменитьТранзакцию(); // Если забыть вызвать эту команду, СУБД все равно сделает "тихий" откат при завершении сеанса
    Сообщить("Что-то пошло не так, но мы продолжаем работу...");
КонецПопытки;

Если внутри транзакции происходит любая СУБД-ошибка (например, нарушение уникальности индексов или блокировка таблиц), СУБД помечает всю текущую транзакцию как поврежденную (doomed). Даже если вы перехватили ошибку в блоке Исключение и не дали программе аварийно завершить работу, транзакция уже никогда не сможет быть зафиксирована. Платформа выполнит ее принудительный откат в конце работы процедуры.

Пошаговая методика диагностики и поиска ошибки

Если вы столкнулись с подобной проблемой, выполните следующие шаги для локализации неисправности:

  1. Анализ стека вызовов: Установите точку останова в конфигураторе на строке ДокументОбъект.Записать(). В момент остановки откройте окно "Стек вызовов" (Call Stack). Посмотрите, какие процедуры привели к этой точке. Если вы видите в стеке обработчики проведения или записи других объектов, значит, вы гарантированно находитесь внутри транзакции.
  2. Проверка транзакционного статуса: Используйте глобальный метод ТранзакцияАктивна() в табло отладчика. Если функция возвращает значение Истина, ваш документ пишется "под прикрытием" чужой транзакции.
  3. Проверка родительской операции: Убедитесь, что объект-инициатор (например, документ, из которого запускается процесс) в конечном итоге успешно записывается или проводится. Если родительский документ не проводится из-за ошибок контроля проведения, ваш подчиненный документ также никогда не запишется.
  4. Использование консоли запросов: Напишите простой запрос к таблице документов Событие в консоли запросов. Это исключит вероятность того, что документ записался, но скрыт от вас какими-то наложенными на форму списка отборами или механизмом RLS (разграничение прав на уровне записей).

Способы решения проблемы

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

Решение №1: Вынос создания документа в обработчик "ПослеЗаписи"

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

Рассмотрим пример реализации на управляемых формах:


&НаКлиенте
Процедура ПослеЗаписи(ПараметрыЗаписи)
    // Родительская транзакция гарантированно завершена и данные сохранены.
    // Теперь создание подчиненного документа абсолютно безопасно.
    СоздатьСобытиеНаСервере(Объект.Ссылка);
КонецПроцедуры

&НаСервереБезКонтекста
Процедура СоздатьСобытиеНаСервере(СсылкаНаИсточник)
    НовоеСобытие = Документы.Событие.СоздатьДокумент();
    НовоеСобытие.Дата = ТекущаяДата();
    НовоеСобытие.ДокументОснование = СсылкаНаИсточник;
    НовоеСобытие.Описание = "Событие создано автоматически после успешной записи документа-основания.";
    
    Попытка
        НовоеСобытие.Записать(РежимЗаписиДокумента.Запись);
    Исключение
        // Здесь мы можем безопасно обработать ошибку, если она возникнет
        ЗаписьЖурналаРегистрации("ПрограммноеСозданиеСобытия",
            УровеньЖурналаРегистрации.Ошибка,,,
            ПодробноеПредставлениеОшибки(ИнформацияОбОшибке()));
    КонецПопытки;
КонецПроцедуры

Решение №2: Использование фоновых заданий

Если создание документа инициируется в коде, который по бизнес-логике обязательно должен находиться в серверном транзакционном модуле (например, в модуле объекта при проведении), и вы не можете перенести вызов на клиентскую форму, используйте механизм фоновых заданий.

Фоновые задания в 1С всегда выполняются в отдельном независимом сеансе на сервере со своим собственным контекстом и, что самое главное, в своей собственной транзакции. Откат основной транзакции никак не затронет фоновое задание.

Разберем, как реализовать этот подход:


// Код внутри модуля проведения основного документа (например, Реализации товаров и услуг)
Процедура ОбработкаПроведения(Отказ, РежимПроведения)
    
    // Основная логика проведения
    // ...
    
    // Нам нужно создать документ "Событие", даже если проведение в итоге откатится.
    // Передаем параметры для создания во фоновое задание.
    ПараметрыПроцедуры = Новый Массив;
    ПараметрыПроцедуры.Добавить(ЭтотОбъект.Ссылка);
    ПараметрыПроцедуры.Добавить(ЭтотОбъект.Контрагент);
    
    ФоновыеЗадания.Выполнить("ФоновыеПроцедуры.СоздатьСобытиеФоном", ПараметрыПроцедуры);

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

Затем в общем модуле ФоновыеПроцедуры (у которого установлены флаги "Сервер" и "Вызов сервера") мы описываем экспортную процедуру создания нашего документа:


Процедура СоздатьСобытиеФоном(СсылкаНаДокумент, Контрагент) Экспорт
    // Данный код выполняется в изолированном сеансе вне родительской транзакции.
    НовоеСобытие = Документы.Событие.СоздатьДокумент();
    НовоеСобытие.Дата = ТекущаяДата();
    НовоеСобытие.Партнер = Контрагент;
    НовоеСобытие.ДокументОснование = СсылкаНаДокумент;
    
    Попытка
        НовоеСобытие.Записать(РежимЗаписиДокумента.Запись);
    Исключение
        ЗаписьЖурналаРегистрации("ФоновоеСозданиеСобытия",
            УровеньЖурналаРегистрации.Ошибка,,,
            ПодробноеПредставлениеОшибки(ИнформацияОбОшибке()));
    КонецПопытки;
КонецПроцедуры

Резюме

Если в вашей практике разработки на 1С программно создаваемые объекты "исчезают" из базы данных, оставляя "битые" ссылки в Журнале регистрации и возвращая номера автонумерации обратно, всегда ищите незафиксированную или откатившуюся транзакцию. Вынос логики создания объектов за пределы транзакции (через клиентские события ПослеЗаписи() или асинхронное выполнение с помощью фоновых заданий) — проверенный стандарт разработки, гарантирующий целостность и сохранность ваших данных.

← На главную