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

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

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

Основная задача — идентифицировать те серии, у которых отсутствуют какие-либо движения в регистрах накопления, и безопасно их удалить.

Анализ проблемы и неверный подход

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

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

Правильное решение через запрос с временными таблицами

Наиболее производительный и правильный способ — использовать пакетный запрос с временными таблицами. Логика будет следующей:

  1. Создаем первую временную таблицу, в которую помещаем все серии, по которым были движения в нужных нам регистрах.
  2. В основном запросе выбираем все серии из справочника СерииНоменклатуры, которые не входят в нашу временную таблицу.

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

Шаг 1. Создание временной таблицы с используемыми сериями

Сначала нам нужно собрать все уникальные серии, которые фигурируют в регистрах накопления. В нашем примере рассмотрим регистр ТоварыНаСкладах. Мы создаем временную таблицу ИспользуемыеСерии, которая будет содержать одно поле — Серия.

Важный момент: чтобы запрос был быстрым, необходимо проиндексировать поле Серия во временной таблице.


// Создаем менеджер временных таблиц
МенеджерВТ = Новый МенеджерВременныхТаблиц;

// Создаем текст первого запроса пакета
Запрос = Новый Запрос;
Запрос.МенеджерВременныхТаблиц = МенеджерВТ;
Запрос.Текст = 
    "ВЫБРАТЬ РАЗЛИЧНЫЕ
    |   ТоварыНаСкладахОстаткиИОбороты.Серия КАК Серия
    |ПОМЕСТИТЬ ИспользуемыеСерии
    |ИЗ
    |   РегистрНакопления.ТоварыНаСкладах.ОстаткиИОбороты КАК ТоварыНаСкладахОстаткиИОбороты
    |
    |ИНДЕКСИРОВАТЬ ПО
    |   Серия";
    
// Выполняем первую часть пакета - создание временной таблицы
Запрос.Выполнить();

В этом запросе мы обращаемся к виртуальной таблице ОстаткиИОбороты регистра ТоварыНаСкладах и выбираем все уникальные (РАЗЛИЧНЫЕ) серии, которые там встречаются. Результат помещается во временную таблицу ИспользуемыеСерии.

Если серийный учет ведется в нескольких регистрах (например, ТоварыВРезервеНаСкладах, ТоварыКОтгрузке и т.д.), их все нужно объединить в этой временной таблице с помощью конструкции ОБЪЕДИНИТЬ ВСЕ.

Шаг 2. Выборка неиспользуемых серий и их удаление

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


// Второй запрос пакета - выборка "мусорных" серий
Запрос.Текст = 
    "ВЫБРАТЬ
    |   СерииНоменклатуры.Ссылка
    |ИЗ
    |   Справочник.СерииНоменклатуры КАК СерииНоменклатуры
    |ГДЕ
    |   СерииНоменклатуры.Ссылка НЕ В (ВЫБРАТЬ
    |           ИспользуемыеСерии.Серия
    |       ИЗ
    |           ИспользуемыеСерии)
    |   И СерииНоменклатуры.Владелец = &Владелец"; // Дополнительное условие для отбора

// Устанавливаем параметр, если нужно удалить серии для конкретной номенклатуры
// Запрос.УстановитьПараметр("Владелец", НужнаяНоменклатура);

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

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

Рассмотрим ключевые моменты этого кода:

Итоговый код обработки

Объединим все шаги в единую процедуру, которую можно использовать в вашей обработке.


&НаСервере
Процедура УдалитьНеиспользуемыеСерииНаСервере()
    
    // 1. Создание и заполнение временной таблицы используемых серий
    МенеджерВТ = Новый МенеджерВременныхТаблиц;
    Запрос = Новый Запрос;
    Запрос.МенеджерВременныхТаблиц = МенеджерВТ;

    Запрос.Текст =
        "ВЫБРАТЬ РАЗЛИЧНЫЕ
        |   ТоварыНаСкладахОстаткиИОбороты.Серия КАК Серия
        |ПОМЕСТИТЬ ИспользуемыеСерии
        |ИЗ
        |   РегистрНакопления.ТоварыНаСкладах.ОстаткиИОбороты КАК ТоварыНаСкладахОстаткиИОбороты
        |
        |ИНДЕКСИРОВАТЬ ПО
        |   Серия";
        
    Запрос.Выполнить();

    // 2. Выборка и удаление серий, которых нет во временной таблице
    Запрос.Текст =
        "ВЫБРАТЬ
        |   СерииНоменклатуры.Ссылка
        |ИЗ
        |   Справочник.СерииНоменклатуры КАК СерииНоменклатуры
        |ГДЕ
        |   СерииНоменклатуры.Ссылка НЕ В (ВЫБРАТЬ
        |           ИспользуемыеСерии.Серия
        |       ИЗ
        |           ИспользуемыеСерии)";

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

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

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

Важное дополнение: операция удаления обернута в транзакцию (НачатьТранзакцию() / ЗафиксироватьТранзакцию() / ОтменитьТранзакцию()). Это гарантирует, что либо все возможные серии будут удалены, либо, в случае критической ошибки, все изменения будут отменены, и база данных останется в целостном состоянии.

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

← На главную