В практике разработки на платформе «1С:Предприятие 8.3» встречается редкая, но крайне неприятная ситуация: при программном создании движений в дополнительном (кастомном) регистре бухгалтерии записи ведут себя нестабильно. Типичный сценарий выглядит так: при первом проведении документа проводки создаются корректно, а при повторном (перепроведении) — бесследно исчезают. При еще одном перепроведении они снова появляются, создавая своеобразный «эффект четности».
В данной статье мы разберем, почему возникает эта проблема, как работает механизм фиксации транзакций в 1С и какими способами можно гарантировать сохранность данных в регистрах.
Рассмотрим ситуацию подробнее. Разработчик добавляет в конфигурацию (например, «1С:Бухгалтерия 3.0») дублирующий регистр бухгалтерии. Его задача — зеркально отражать записи основного регистра Хозрасчетный. Для этого в обработчик события ПриЗаписи набора записей регистра Хозрасчетный добавляется код, который создает набор записей для второго регистра, устанавливает регистратор и копирует в него данные.
Проблема проявляется в том, что в некоторых документах (таких как «Закрытие месяца» или кастомные обмены) каждое второе проведение очищает движения. Выясним причину этого поведения. Проанализируем стек вызовов в момент удаления записей. Часто оказывается, что в момент очистки стек пуст, либо содержит единственный вызов обработчика ПриЗаписи. Это указывает на то, что инициатором записи выступает не ваш программный код, а ядро платформы (внутренний механизм фиксации транзакций).
Когда мы работаем с документом в 1С, у него есть коллекция Движения. Платформа ожидает, что работа с регистрами будет вестись именно через эту коллекцию. Если же мы создаем набор записей «со стороны» через РегистрыБухгалтерии.ВашРегистр.СоздатьНаборЗаписей(), возникает опасная рассинхронизация:
Движения ваш кастомный регистр, скорее всего, пуст.Движения.ВашРегистр записей нет (набор пустой), и «сбрасывает» это пустое состояние в базу данных, затирая ваши только что записанные данные.Этот процесс часто срабатывает именно при перепроведении, так как платформа пытается оптимизировать запись и сравнивает текущее состояние БД с состоянием объекта в памяти.
Проанализируем свойство метаданных документа Удаление движений. Если установлено значение Удалять автоматически, платформа перед проведением очищает все регистры, где данный документ является регистратором. Однако, если запись в кастомный регистр происходит не в основной транзакции проведения, а вложенно через обработчики других регистров, механизмы кэширования могут сработать некорректно. Платформа «видит» наличие записей в конце транзакции, которые она не планировала записывать, и принудительно вызывает очистку пустого набора для синхронизации состояния.
Самый простой, хотя и несколько «силовой» метод — это гарантировать, что перед началом проведения документ не имеет движений в проблемном регистре. Рассмотрим пример кода, который можно вставить в начало процедуры проведения или в подписку ПередЗаписью документа:
Процедура ПередЗаписьюДокумента(Источник, Отказ, РежимЗаписи, РежимПроведения)
Если РежимЗаписи = РежимЗаписиДокумента.Проведение Тогда
// Очищаем движения дублирующего регистра перед началом транзакции
Набор = РегистрыБухгалтерии.ВашДополнительныйРегистр.СоздатьНаборЗаписей();
Набор.Отбор.Регистратор.Установить(Источник.Ссылка);
Набор.Записать(); // Записываем пустой набор
КонецЕсли;
КонецПроцедуры
Этот метод обнуляет «счетчик четности» платформы. Поскольку при старте проведения записей в базе уже нет, платформа не будет пытаться их «финализировать» в конце транзакции пустым набором.
Разберем более правильный с точки зрения архитектуры 1С подход. Нужно «сообщить» платформе, что ваш регистр управляется документом. Для этого в процедуре проведения (или в обработчике, где есть доступ к объекту документа) необходимо явно обращаться к коллекции движений.
Даже если вы записываете регистр своим кодом, добавьте следующую строку:
// В контексте объекта документа (например, в обработке проведения)
Движения.ВашДополнительныйРегистр.Записывать = Истина;
Установка этого флага заставляет платформу считать, что набор данных в коллекции Движения является актуальным. Если вы при этом загружаете в этот набор данные, платформа сама корректно зафиксирует их в конце транзакции и не будет вызывать дополнительные очистки.
Выясним, почему именно «Закрытие месяца» часто становится источником проблем. Этот документ работает в режиме «Записывать выбранные». В отличие от обычных документов, он может записывать движения регистра Хозрасчетный порциями в разных фоновых заданиях или разных транзакциях внутри одного процесса.
Если ваша логика копирования записей находится в ПриЗаписи набора регистра Хозрасчетный, она может вызываться несколько раз. Рассмотрим ситуацию:
Чтобы избежать этого, проанализируем использование свойства ОбменДанными.Загрузка и проверку на пустой набор перед записью во второй регистр:
Процедура ПриЗаписиНабораХозрасчетный(Источник, Отказ)
Если Источник.ОбменДанными.Загрузка Тогда
Возврат;
КонецЕсли;
// Проверяем, не пустой ли набор мы пытаемся скопировать
Если Источник.Количество() = 0 Тогда
// Если основной набор пуст, нужно быть осторожным,
// чтобы не затереть данные, записанные ранее в этой же транзакции
Возврат;
КонецЕсли;
// Логика копирования в ВашДополнительныйРегистр...
КонецПроцедуры
Если проблема сохраняется, выполните следующие шаги для выявления «невидимки», очищающего регистр:
ПередЗаписью.ЭтотОбъект. Проверьте свойство Записывать и количество строк в наборе. Если строк 0 — вы нашли момент «стирания».Важный момент: Старайтесь избегать записи движений в обработчике ПриЗаписи других регистров. Это создает «эффект матрешки» в транзакциях. Безопаснее использовать событие ПередЗаписью основного набора или перенести логику в подписку на событие ОбработкаПроведения документа, где можно централизованно управлять всеми движениями через коллекцию ЭтотОбъект.Движения.