Как восстановить утерянные движения регистра накопления при случайном удалении связи с регистратором в 1С

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

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

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

Почему данные исчезли и как работает платформа 1С на уровне СУБД

Прежде чем перейти к методам восстановления, проанализируем физику процесса. В СУБД для каждого регистра накопления создается несколько таблиц: таблица движений (например, _AccumRegXXXX) и таблицы итогов. В таблице движений присутствует поле ссылки на регистратор (_Recorder). Когда при обновлении мы исключаем документ из списка регистраторов, платформа выполняет команду реструктуризации базы данных. На уровне SQL это выливается в удаление всех записей из таблицы движений, где тип регистратора соответствует исключенному документу.

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


Способ 1. Использование стандартной обработки «Выгрузка и загрузка данных XML»

Это самый простой, быстрый и безопасный способ, не требующий написания сложного кода. Важно не путать эту обработку с инструментом «Универсальный обмен данными в формате XML». Последняя требует разработки правил обмена в конфигурации «Конвертация данных», что усложняет процесс.

Обработка «Выгрузка и загрузка данных XML» (поставляется на ИТС, имя файла обычно v8Exchan.epf или ВыгрузкаЗагрузкаДанныхXML83.epf) работает напрямую с метаданными идентичных конфигураций. Поскольку мы восстанавливаем данные из бэкапа той же самой базы, структуры метаданных и Уникальные Идентификаторы (UUID) документов полностью совпадают.

Рассмотрим пошагово порядок действий:

  1. Развернем резервную копию базы данных (бэкап) на тестовом сервере или в локальном файловом варианте.
  2. В рабочей конфигурации вернем галочку регистратора нашему документу в свойствах регистра накопления и выполним обновление базы данных (F7).
  3. Запустим обработку «Выгрузка и загрузка данных XML» в **базе-бэкапе**.
  4. В списке объектов для выгрузки найдем наш пострадавший регистр накопления.
  5. Важный нюанс: Обязательно снимем флаг «Выгружать при необходимости» (или аналогичную настройку автовыгрузки связанных объектов). Нам нужно выгрузить исключительно записи самого регистра, без повторного переноса документов, справочников договоров и контрагентов. Если этого не сделать, обработка потащит за собой гигабайты лишних данных и может перезаписать актуальные реквизиты документов в рабочей базе.
  6. Настроим отбор по регистратору (тип документа), если в регистр пишут и другие документы, движения по которым не пострадали.
  7. Выполним выгрузку данных в файл XML.
  8. Откроем рабочую базу, запустим ту же обработку на вкладке «Загрузка» и укажем полученный файл. Платформа запишет движения, и они автоматически привяжутся к существующим документам по их внутренним идентификаторам (UUID).

Способ 2. Программный перенос движений через текстовую сериализацию (без COM-соединения)

Если базы находятся на разных серверах, доступ через COM-соединение затруднен или обработка XML аварийно завершается из-за нехватки памяти (актуально для больших баз данных), мы можем использовать механизм внутренней сериализации 1С. Мы выгрузим движения в текстовый файл в базе-копии и загрузим их в рабочей базе.

Выясним, как написать код экспорта в базе-копии. Создадим внешнюю обработку и выполним следующий код:


// В базе-копии (бэкапе)
Процедура ВыгрузитьДвиженияВФайл()
    
    ПутьКФайлу = "C:\temp\movements_backup.txt";
    ТекстДанных = Новый ТекстовыйДокумент;
    
    // Выбираем только те документы, движения по которым были утеряны
    Запрос = Новый Запрос;
    Запрос.Текст = 
        "ВЫБРАТЬ
        |   Док.Ссылка КАК Ссылка
        |ИЗ
        |   Документ.ПлатежныйОрдерСписаниеДенежныхСредств КАК Док
        |ГДЕ
        |   Док.Проведен = ИСТИНА";
        
    РезультатЗапроса = Запрос.Выполнить();
    Выборка = РезультатЗапроса.Выбрать();
    
    Пока Выборка.Следующий() Цикл
        
        // Создаем набор записей по конкретному регистратору
        НаборЗаписей = РегистрыНакопления.ИмяВашегоРегистра.СоздатьНаборЗаписей();
        НаборЗаписей.Отбор.Регистратор.Установить(Выборка.Ссылка);
        НаборЗаписей.Прочитать();
        
        Если НаборЗаписей.Количество() > 0 Тогда
            // Превращаем таблицу движений в строку внутреннего формата 1С
            ТаблицаСтрокой = ЗначениеВСтрокуВнутр(НаборЗаписей.Выгрузить());
            // Записываем UUID документа и сериализованные движения через разделитель
            СтрокаЗаписи = Строка(Выборка.Ссылка.УникальныйИдентификатор()) + "|" + ТаблицаСтрокой;
            ТекстДанных.ДобавитьСтроку(СтрокаЗаписи);
        КонецЕсли;
        
    КонецЦикла;
    
    ТекстДанных.Записать(ПутьКФайлу, КодировкаТекста.UTF8);
    Сообщить("Выгрузка успешно завершена!");
    
КонецПроцедуры

Теперь рассмотрим, как выполнить загрузку этих данных в рабочей базе. Обратите внимание на использование важнейшего свойства ОбменДанными.Загрузка = Истина. Оно позволяет записывать наборы данных напрямую в СУБД, игнорируя платформенные проверки, подписки на события и ограничения закрытых периодов.


// В рабочей базе
Процедура ЗагрузитьДвиженияИзФайла()
    
    ПутьКФайлу = "C:\temp\movements_backup.txt";
    ТекстДанных = Новый ТекстовыйДокумент;
    ТекстДанных.Прочитать(ПутьКФайлу, КодировкаТекста.UTF8);
    
    Для НомерСтроки = 1 По ТекстДанных.КоличествоСтрок() Цикл
        
        ТекущаяСтрока = ТекстДанных.ПолучитьСтроку(НомерСтроки);
        Если ПустаяСтрока(ТекущаяСтрока) Тогда
            Продолжить;
        КонецЕсли;
        
        // Находим позицию разделителя
        ПозицияРазделителя = Найти(ТекущаяСтрока, "|");
        Если ПозицияРазделителя = 0 Тогда
            Продолжить;
        КонецЕсли;
        
        СтрокаUUID = Лев(ТекущаяСтрока, ПозицияРазделителя - 1);
        СтрокаТаблицы = Сред(ТекущаяСтрока, ПозицияРазделителя + 1);
        
        // Восстанавливаем ссылку на документ по его уникальному идентификатору
        УИД = Новый УникальныйИдентификатор(СтрокаUUID);
        СсылкаНаРегистратор = Документы.ПлатежныйОрдерСписаниеДенежныхСредств.ПолучитьСсылку(УИД);
        
        // Десериализуем таблицу движений из строки
        ТаблицаДвижений = ЗначениеИзСтрокиВнутр(СтрокаТаблицы);
        
        // Записываем набор в базу данных
        НаборЗаписей = РегистрыНакопления.ИмяВашегоРегистра.СоздатьНаборЗаписей();
        НаборЗаписей.Отбор.Регистратор.Установить(СсылкаНаРегистратор);
        НаборЗаписей.Загрузить(ТаблицаДвижений);
        
        // Отключаем бизнес-логику проведения для обхода блокировок прошлых периодов
        НаборЗаписей.ОбменДанными.Загрузка = Истина;
        НаборЗаписей.Записать();
        
    КонецЦикла;
    
    Сообщить("Загрузка движений завершена!");
    
КонецПроцедуры

Способ 3. Выборочное проведение в транзакции (если алгоритм проведения не менялся)

Иногда бэкапа под рукой нет или он слишком старый, но сами документы в рабочей базе не пострадали, а алгоритм формирования движений (например, в модуле документа или в подписке на событие) остался прежним. В этом случае мы можем принудительно перепровести только нужные документы.

Однако простое перепроведение документов за прошлые года (в теме упоминается период с 2008 по 2018 год) — крайне опасная операция. Могут поехать котировки, себестоимость, взаиморасчеты и остатки по другим регистрам, которые не пострадали. Чтобы избежать этого, применим хитрый прием: запустим проведение документа внутри транзакции, прочитаем сформированные движения по нашему регистру в память, а затем отменим транзакцию (выполнив откат изменений по всем остальным регистрам).

Рассмотрим программную реализацию этого алгоритма:


Процедура ВосстановитьДвиженияВыборочнымПроведением()
    
    ИмяРегистра = "ИмяВашегоРегистра";
    
    // Выбираем документы, у которых пустые движения по нашему регистру
    Запрос = Новый Запрос;
    Запрос.Текст = 
        "ВЫБРАТЬ
        |   Док.Ссылка КАК Ссылка
        |ИЗ
        |   Документ.ПлатежныйОрдерСписаниеДенежныхСредств КАК Док
        |ЛЕВОЕ СОЕДИНЕНИЕ
        |   РегистрНакопления.ИмяВашегоРегистра КАК ДвиженияРегистра
        |   ПО ДвиженияРегистра.Регистратор = Док.Ссылка
        |ГДЕ
        |   Док.Проведен = ИСТИНА
        |   И ДвиженияРегистра.Регистратор ЕСТЬ NULL";
        
    Выборка = Запрос.Выполнить().Выбрать();
    
    Пока Выборка.Следующий() Цикл
        
        ДокСсылка = Выборка.Ссылка;
        ДокОбъект = ДокСсылка.ПолучитьОбъект();
        
        НачатьТранзакцию();
        
        Попытка
            // Проводим документ, чтобы сработали стандартные механизмы и подписки
            ДокОбъект.Записать(РежимЗаписиДокумента.Проведение);
            
            // Читаем движения только по нашему целевому регистру
            ДокОбъект.Движения[ИмяРегистра].Прочитать();
            ТаблицаДвиженийРегистра = ДокОбъект.Движения[ИмяРегистра].Выгрузить();
            
            // Отменяем транзакцию, чтобы изменения по другим регистрам не записались в базу
            ОтменитьТранзакцию();
            
        Исключение
            // В случае ошибки обязательно откатываем транзакцию
            ОтменитьТранзакцию();
            ЗаписьЖурналаРегистрации("ВосстановлениеДвижений", УровеньЖурналаРегистрации.Ошибка, , , 
                "Ошибка обработки документа: " + ДокСсылка + " Описание: " + ОписаниеОшибки());
            Продолжить;
        КонецПопытки;
        
        // Теперь записываем полученную таблицу движений напрямую в базу
        НаборЗаписей = РегистрыНакопления[ИмяРегистра].СоздатьНаборЗаписей();
        НаборЗаписей.Отбор.Регистратор.Установить(ДокСсылка);
        НаборЗаписей.Загрузить(ТаблицаДвиженийРегистра);
        
        // Запись в режиме обмена данными исключает повторные проверки периодов
        НаборЗаписей.ОбменДанными.Загрузка = Истина;
        НаборЗаписей.Записать();
        
    КонецЦикла;
    
    Сообщить("Выборочное восстановление завершено!");
    
КонецПроцедуры

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


Способ 4. Перенос через SQL-запросы (для баз большого объема)

Если база данных работает в клиент-серверном режиме под управлением MS SQL Server или PostgreSQL, а ее объем составляет сотни гигабайт (например, более 300 Гб), выгрузка через XML или текстовые файлы может вызвать переполнение оперативной памяти сервера или выполняться слишком долго.

В этом случае системные администраторы СУБД могут перенести записи напрямую из таблицы бэкапа в рабочую таблицу регистра накопления с помощью SQL-запроса INSERT INTO ... SELECT.

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

Для исправления итогов после SQL-переноса обязательно выполните следующие действия в режиме «1С:Предприятие»:

  1. Откройте меню Администрирование -> Тестирование и исправление.
  2. Установите флаг напротив пункта «Пересчет итогов».
  3. Запустите выполнение операции. Платформа пересчитает агрегированные таблицы остатков и оборотов на основании восстановленных физических записей регистра.

Как предотвратить подобные проблемы в будущем

Ситуация, когда доработанные связи затираются при обновлении конфигурации поставщика — это архитектурная уязвимость старых методов разработки. Чтобы навсегда застраховать себя от подобных инцидентов, используйте современные паттерны доработки конфигураций:

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

← На главную