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