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

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

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

Выясним причины возникновения движений у непроведенных документов

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

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

Подготовка к решению проблемы: Важность резервной копии

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

Стандартные и проверенные способы решения

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

  1. Повторное проведение и отмена проведения документа

    Один из самых простых и часто работающих методов, упомянутый в сообщении 6, заключается в попытке провести документ, а затем снова снять его с проведения. Мы предполагаем, что если документ был в некорректном состоянии, то при его проведении система пересчитает и перезапишет все движения. А при последующей отмене проведения – корректно очистит их.

    Порядок действий:

    1. Откройте проблемный документ.
    2. Если он непроведен, попробуйте его провести.
    3. Сразу после проведения снимите документ с проведения.
    4. Проверьте регистр накопления (в нашем случае РасчетыСПоставщикамиПоСрокам), остались ли движения.

    Если это не помогло, мы перейдем к следующему шагу.

  2. "Тестирование и исправление" информационной базы

    Этот встроенный механизм платформы 1С может помочь исправить многие ошибки, включая битые ссылки и некорректную целостность данных. Мы рекомендуем запускать его с установленной галочкой "Проверка ссылочной целостности" и "Удаление объектов ссылающихся на несуществующие".

    Порядок действий:

    1. Запустите 1С:Предприятие в режиме "Конфигуратор".
    2. Перейдите в меню "Администрирование" -> "Тестирование и исправление...".
    3. В открывшемся окне установите флажки "Проверка ссылочной целостности" и "Реструктуризация таблиц информационной базы". Можно также установить "Пересчет итогов" и "Сжатие таблиц".
    4. Для устранения битых ссылок выберите действие "Удалять ссылки на не существующие объекты" или "Создавать объекты".
    5. Нажмите "Выполнить". Учтите, что эта операция может занять продолжительное время и требует монопольного режима работы.

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

Программное удаление движений: работа с наборами записей регистра

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

Мы будем работать с объектами НаборЗаписей и коллекцией движений документа, чтобы очистить некорректные записи.

Метод 1: Очистка набора записей регистра с отбором (Сообщение 3)

Этот подход основан на получении объекта НаборЗаписей для конкретного регистра, установке отбора по регистратору (проблемному документу), а затем очистке и записи пустого набора записей. Это эффективно удаляет все записи, связанные с конкретным документом в выбранном регистре.

Рассмотрим, как мы можем реализовать этот метод:


// Предположим, у нас есть ссылка на проблемный документ.
// Вы можете получить ее, например, через поле формы или из запроса.
// Для примера, пусть это будет переменная СсылкаНаПроблемныйДокумент типа ДокументСсылка.
// Например: СсылкаНаПроблемныйДокумент = Документы.ПриобретениеТоваровУслуг.НайтиПоНомеру("0000-000001", Дата("20230101"));

// 1. Мы должны указать регистр, в котором обнаружены некорректные движения.
// В нашем случае, это "Расчеты с поставщиками по срокам".
Перем РегистрДвижений;
Попытка
    РегистрДвижений = РегистрыНакопления.РасчетыСПоставщикамиПоСрокам; // Получаем объект-менеджер регистра
Исключение
    Сообщить("Регистр накопления 'РасчетыСПоставщикамиПоСрокам' не найден в метаданных. Проверьте имя регистра.");
    Возврат;
КонецПопытки;

// 2. Создаем новый набор записей для этого регистра.
НаборЗаписей = РегистрДвижений.СоздатьНаборЗаписей();

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

// 4. Прочитываем существующие записи, соответствующие отбору.
НаборЗаписей.Прочитать();

// 5. Очищаем все записи, которые были прочитаны. Теперь набор записей пуст.
НаборЗаписей.Очистить();

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

Мы должны убедиться, что СсылкаНаПроблемныйДокумент корректно получена из контекста вашей проблемы. Например, если вы разрабатываете внешнюю обработку, вы можете добавить поле ввода типа "ДокументСсылка" на форму и передавать его значение. Этот метод является одним из самых надежных для очистки движений конкретного документа в конкретном регистре.

Метод 2: Итерация по движениям объекта документа (Сообщения 8 и 10)

Этот подход предполагает обращение непосредственно к коллекции движений документа ДокОбъект.Движения и попытку очистить их. Коллекция Движения документа представляет собой набор объектов НаборЗаписей для всех регистров, в которые документ может делать движения.

Давайте проанализируем примеры кода из сообщений форума и предложим корректный подход.

Вариант кода из сообщения 8:


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

Если ДокОбъект <> Неопределено Тогда
    Для Каждого ТекущийНаборДвижений Из ДокОбъект.Движения Цикл // Итерируем по всем наборам записей (по регистрам)
        Попытка
            ТекущийНаборДвижений.Очистить(); // Очищаем записи в текущем наборе движений
            ТекущийНаборДвижений.Записать(); // Записываем пустой набор (по умолчанию Замещать = Истина)
            Сообщить("Движения для регистра " + ТекущийНаборДвижений.Имя + " очищены.");
        Исключение
            Сообщить("Ошибка при очистке движений регистра " + ТекущийНаборДвижений.Имя + ": " + ОписаниеОшибки());
        КонецПопытки;
    КонецЦикла;
Иначе
    Сообщить("Объект документа не найден.");
КонецЕсли;

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

Вариант кода из сообщения 10 и его исправление:

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


// Исходный код из сообщения 10 (с комментариями о проблеме):
// Предполагаем, что переменная "Объект" является объектом формы или другим контекстом.
// ДокОбъект = Объект.Реквизит1.Ссылка.ПолучитьОбъект();
// Для каждого Движения Из ДокОбъект.Движения Цикл // Внешний цикл по регистрам
//     // Здесь начинается проблемный внутренний цикл.
//     // Он также итерирует по ВСЕМ регистрам, вместо того чтобы работать с 'Движения' из внешнего цикла.
//     Для Каждого НаборЗаписей ИЗ ДокОбъект.Движения Цикл
//         НаборЗаписей.Прочитать();
//         Если НаборЗаписей.Количество > 0 Тогда
//             Попытка
//                 НаборЗаписей.Очистить();
//                 НаборЗаписей.Записать(Истина);
//             Исключение
//                 Сообщить(ОписаниеОшибки);
//             КонецПопытки;
//         КонецЕсли;
//     КонецЦикла;
//     Сообщить(Движения); // Вывод информации (для отладки)
//     Движения.Очистить(); // Повторная попытка очистки для внешнего цикла
//     Движения.Записать(); // Повторная попытка записи для внешнего цикла
// КонецЦикла;

// Корректный подход при работе через коллекцию движений документа:
// Мы предполагаем, что у нас есть переменная "ДокументСсылка", которая является ссылкой на проблемный документ.
Перем ДокОбъект;
Попытка
    ДокОбъект = ДокументСсылка.ПолучитьОбъект();
Исключение
    Сообщить("Не удалось получить объект документа по ссылке: " + ОписаниеОшибки());
    Возврат;
КонецПопытки;

Если ДокОбъект <> Неопределено Тогда
    Для Каждого ИмяРегистра Из ДокОбъект.Движения Цикл // ИмяРегистра здесь фактически является НаборомЗаписей
        Попытка
            // Получаем конкретный набор записей для текущего регистра по его имени
            ТекущийНаборЗаписей = ДокОбъект.Движения[ИмяРегистра.Имя];

            // Перед очисткой и записью желательно прочитать текущее состояние,
            // чтобы убедиться, что мы работаем с актуальными данными.
            ТекущийНаборЗаписей.Прочитать();

            Если ТекущийНаборЗаписей.Количество > 0 Тогда
                ТекущийНаборЗаписей.Очистить(); // Очищаем все записи для данного регистратора
                ТекущийНаборЗаписей.Записать(Истина); // Записываем пустой набор, замещая старые записи
                Сообщить("Движения для регистра " + ИмяРегистра.Имя + " успешно очищены.");
            Иначе
                Сообщить("В регистре " + ИмяРегистра.Имя + " не найдено движений для документа.");
            КонецЕсли;
        Исключение
            Сообщить("Ошибка при очистке движений регистра " + ИмяРегистра.Имя + ": " + ОписаниеОшибки());
        КонецПопытки;
    КонецЦикла;
Иначе
    Сообщить("Объект документа не найден.");
КонецЕсли;

В этом исправленном коде мы явно работаем с элементом коллекции ДокОбъект.Движения, который представляет собой НаборЗаписей для конкретного регистра. Мы читаем его, очищаем и записываем, используя параметр Истина для замещения. Этот подход более логичен и избегает избыточности вложенных циклов.

Поиск и удаление "битых" записей или задвоенных данных

Проблема задвоения данных (как было в сообщении 1) или наличие "битых" записей (сообщение 5) требует более глубокого анализа. Задвоения могут быть особенно коварны, так как они искажают итоговые показатели.

Анализ причин задвоений

Мы выясняем, что чаще всего задвоения возникают из-за следующих факторов:

  1. Человеческий фактор: Повторный ввод одного и того же документа или операции.
  2. Ошибки обмена данными: При загрузке или синхронизации данных из другой системы или базы, если механизмы сопоставления объектов настроены некорректно, могут создаваться дубли.
  3. Программные ошибки: Некорректный код, который записывает одни и те же движения несколько раз.

Использование запросов для поиска аномальных случаев

Если мы подозреваем, что проблема кроется в загрузке/обмене данных (сообщение 7) или в общей нестабильности, мы можем использовать запросы для поиска аномальных записей в регистрах. Это позволит нам выявить записи с непроведенными регистраторами, помеченными на удаление регистраторами или даже с отсутствующими регистраторами (<Объект не найден>).

Рассмотрим пример запроса к регистру накопления РасчетыСПоставщикамиПоСрокам (для массовых операций подойдет обработка Удаление и обработка записей регистров накопления):


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

РезультатЗапроса = Запрос.Выполнить();
Выборка = РезультатЗапроса.Выбрать();

Пока Выборка.Следующий() Цикл
    // Мы обнаружили аномальное движение. Теперь нам нужно решить, что с ним делать.
    Сообщить("Обнаружено аномальное движение. Регистратор: " + Строка(Выборка.Регистратор)
            + ", Период: " + Строка(Выборка.Период));

    // Вариант 1: Если Регистратор существует и он непроведен или помечен на удаление
    Если ТипЗнч(Выборка.Регистратор) <> Тип("Неопределено") И ТипЗнч(Выборка.Регистратор) <> Тип("Null") Тогда
        // Мы можем попытаться применить методы очистки, описанные выше,
        // используя Выборка.Регистратор как СсылкаНаПроблемныйДокумент.
        // Например, вызвать процедуру очистки движений.
        // ОчиститьДвиженияДляДокумента(Выборка.Регистратор, "РасчетыСПоставщикамиПоСрокам");
    Иначе
        // Вариант 2: Если Регистратор является битой ссылкой (NULL).
        // В этом случае мы не можем получить объект документа.
        // Нам нужно удалить записи напрямую из регистра, отфильтровав их по этой битой ссылке.
        РегистрДляОчистки = РегистрыНакопления.РасчетыСПоставщикамиПоСрокам;
        НаборЗаписейДляОчистки = РегистрДляОчистки.СоздатьНаборЗаписей();
        НаборЗаписейДляОчистки.Отбор.Регистратор.Установить(Выборка.Регистратор); // Отбор по NULL или битой ссылке
        Попытка
            НаборЗаписейДляОчистки.Прочитать();
            Если НаборЗаписейДляОчистки.Количество > 0 Тогда
                НаборЗаписейДляОчистки.Очистить();
                НаборЗаписейДляОчистки.Записать(Истина);
                Сообщить("Удалены движения с битой ссылкой в регистре " + РегистрДляОчистки.Метаданные().Имя + ".");
            КонецЕсли;
        Исключение
            Сообщить("Ошибка при удалении битых движений: " + ОписаниеОшибки());
        КонецПопытки;
    КонецЕсли;
КонецЦикла;

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

Универсальные обработки для поиска и удаления дублей

Для поиска и удаления дублирующихся данных или "битых" ссылок в регистрах существуют как типовые, так и внешние обработки, такие как Групповая корректировка записей регистров:

  1. "Поиск и удаление дублей": В типовых конфигурациях 1С (Бухгалтерия, Управление торговлей, ЗУП и др.) есть универсальная обработка "Поиск и удаление дублей". Мы можем использовать ее для устранения задвоений, но она чаще работает с объектами справочников и документов, а не напрямую с записями регистров.
  2. Специализированные внешние обработки: Многие специалисты разрабатывают внешние обработки, которые позволяют более гибко работать с регистрами: искать записи с отсутствующими регистраторами (<Объект не найден>), выявлять дублирующиеся записи по определенным измерениям и удалять их. При использовании таких обработок мы всегда должны убедиться в их надежности и протестировать на резервной копии.

Документ "Регистратор расчетов" и его роль

В сообщениях форума (сообщение 2 и 11) упоминается документ "Регистратор расчетов" как источник проблемы. Мы выясняем, что этот тип документа часто используется в конфигурациях для выполнения регламентных операций по взаиморасчетам, пересчету долгов, распределению платежей и т.д. Он может быть результатом работы фоновых заданий или специфических обменов данными. Если именно этот документ генерирует некорректные движения, нам необходимо:

  1. Тщательно изучить логику его работы в вашей конфигурации: как он создается, какие движения делает, какие модули объектов или подписки на события с ним связаны.
  2. Проверить, не является ли он результатом некорректного обмена данными.
  3. Если проблема связана с доработками, возможно, нам придется исправить код в модулях проведения или обработки этого документа.

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

Заключение и рекомендации

Решение проблемы движений у непроведенного документа в 1С:Предприятие часто требует комплексного подхода. Мы рассмотрели различные методы, от простых попыток перепроведения до сложного анализа кода и прямого манипулирования регистрами. Подведем итоги:

  1. Всегда начинайте с резервной копии. Это золотое правило, которое нельзя игнорировать.
  2. Постепенность. Мы применяем наиболее безопасные и простые методы (перепроведение, "Тестирование и исправление") перед тем, как переходить к прямому программному вмешательству.
  3. Диагностика причин. Мы всегда стараемся выяснить первопричину возникновения проблемы, чтобы предотвратить ее повторение в будущем. Использование запросов для поиска аномалий в регистрах является мощным инструментом диагностики.
  4. Тщательная проверка. После каждого шага по устранению проблемы мы тщательно проверяем результат, анализируя регистры и отчеты, чтобы убедиться в корректности внесенных изменений.
  5. Понимание конфигурации. Если проблема связана со специфическими документами или доработками (как в случае с "Регистратором расчетов"), мы углубленно изучаем логику их работы.

Работая системно и последовательно, мы сможем эффективно устранить проблему некорректных движений и восстановить целостность данных в нашей информационной базе 1С:Предприятие.

← На главную