Программная пометка удаления в 1С 8.3: от одного объекта до тысяч
Содержание
Чтобы пометить объект на удаление программно, получите его объект и вызовите метод УстановитьПометкуУдаления(Истина): Ссылка.ПолучитьОбъект().УстановитьПометкуУдаления(Истина). Метод сам записывает объект, отдельный Записать() не нужен. Снимается пометка тем же методом с параметром Ложь.
Механизм общий для платформы 8.3 и не зависит от конфигурации: код одинаково работает в Бухгалтерии 3.0, ЗУП 3.1, УТ 11 и самописных базах. Различаются только имена справочников и документов, которые вы подставляете в запросы.
Как устроена пометка удаления на уровне платформы
Пометка удаления — это стандартный реквизит ПометкаУдаления (тип Булево) у ссылочных объектов. Объект с пометкой остаётся в базе, ссылки на него не рвутся. Физически запись исчезает только после удаления помеченных объектов, когда платформа проверит, что на неё никто не ссылается.
Пометку поддерживают:
- 📁 справочники;
- 📄 документы;
- 📊 планы видов характеристик, планы счетов, планы видов расчёта;
- 🔄 планы обмена, бизнес-процессы и задачи.
У регистров, констант и перечислений пометки нет. Записи регистров удаляют через набор записей, а не помечают.
Три способа избавиться от объекта в коде:
| Способ | Что происходит | Проверка ссылок | Когда применять |
|---|---|---|---|
УстановитьПометкуУдаления(Истина) | Ставится флаг, объект остаётся | При удалении помеченных | Почти всегда |
Удалить() | Объект удаляется сразу | Нет | Мусор без ссылок: черновики, временные элементы |
УдалитьОбъекты(Массив, Истина) | Удаление массива с контролем ссылок | Да | Финальная очистка из кода |
Права тоже разные. Программной пометке нужно право «Изменение» на объект. Методу Удалить() — право «Удаление». Интерактивные права («Интерактивная пометка удаления» и др.) на код не влияют: они управляют только кнопками в формах.
Пометка одного объекта: метод УстановитьПометкуУдаления
Справочник
&НаСервере
Процедура ПометитьЭлементНаСервере(Ссылка)
Объект = Ссылка.ПолучитьОбъект();
Если Объект = Неопределено Тогда
Возврат; // объект уже удалён физически
КонецЕсли;
Если Не Объект.ПометкаУдаления Тогда
Объект.УстановитьПометкуУдаления(Истина);
КонецЕсли;
КонецПроцедуры
Метод работает только с объектом, у ссылки его нет. Поэтому код всегда выполняется на сервере: на клиенте ПолучитьОбъект() недоступен.
У иерархического справочника второй параметр — ВключаяПодчиненные. По умолчанию он равен Истина: пометив группу, вы помечаете всё её содержимое. Если нужна пометка только самой группы, передайте Ложь явно:
Объект.УстановитьПометкуУдаления(Истина, Ложь);
Документ
У документа поведение другое. Если документ проведён, установка пометки отменяет проведение и удаляет его движения. Остатки, взаиморасчёты и проводки по нему пропадут в момент пометки, а не при удалении.
Объект = ДокументСсылка.ПолучитьОбъект();
Если Объект.Проведен Тогда
// движения удалятся вместе с проведением
КонецЕсли;
Объект.УстановитьПометкуУдаления(Истина);
Снятие пометки (УстановитьПометкуУдаления(Ложь)) не проводит документ обратно. Его нужно провести отдельно: Объект.Записать(РежимЗаписиДокумента.Проведение).
Что срабатывает при пометке
Установка пометки записывает объект. Значит, выполняются обработчики модуля объекта ПередЗаписью и ПриЗаписи, подписки на события и регистрация изменений в планах обмена. В ПередЗаписью можно отличить пометку от обычной записи:
Процедура ПередЗаписью(Отказ)
Если ПометкаУдаления И Не Ссылка.ПометкаУдаления Тогда
// объект помечают прямо сейчас
КонецЕсли;
КонецПроцедуры
Эту проверку удобно использовать для запрета: например, не давать пометить элемент, пока по нему есть открытые заказы. Установите Отказ = Истина, и пометка не запишется, а код вызывающей стороны получит исключение.
Метод Удалить(): когда он нужен вместо пометки
Удалить() убирает объект из базы сразу, без пометки и без проверки ссылок. Если на удалённый элемент ссылались документы, в них останутся «битые» ссылки вида <Объект не найден> (123:abc...).
Объект = Ссылка.ПолучитьОбъект();
Объект.Удалить();
Для документа Удалить() убирает и его движения.
Когда этот метод уместен:
- ✅ временные элементы, которые создала ваша же обработка и на которые точно нет ссылок;
- ✅ загрузка с откатом: удалить только что созданные дубли в рамках того же сеанса;
- ❌ рабочие справочники (номенклатура, контрагенты, сотрудники) — только пометка;
- ❌ документы прошлых периодов — только пометка.
Если сомневаетесь, на что ссылается объект, проверьте перед Удалить() функцией НайтиПоСсылкам(). Пример — в разделе про очистку ниже.
Массовая пометка на удаление через запрос и выборку
Массовая задача решается в два шага: запрос отбирает ссылки, цикл помечает каждую. Пример помечает элементы номенклатуры, в наименовании которых есть «(архив)»:
&НаСервере
Процедура ПометитьАрхивнуюНоменклатуруНаСервере()
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ
| Номенклатура.Ссылка КАК Ссылка
|ИЗ
| Справочник.Номенклатура КАК Номенклатура
|ГДЕ
| НЕ Номенклатура.ПометкаУдаления
| И Номенклатура.Наименование ПОДОБНО &Шаблон";
Запрос.УстановитьПараметр("Шаблон", "%(архив)%");
Выборка = Запрос.Выполнить().Выбрать();
Помечено = 0;
Пропущено = 0;
Пока Выборка.Следующий() Цикл
Попытка
Объект = Выборка.Ссылка.ПолучитьОбъект();
Объект.Заблокировать();
Объект.УстановитьПометкуУдаления(Истина);
Помечено = Помечено + 1;
Исключение
Пропущено = Пропущено + 1;
ЗаписьЖурналаРегистрации("Пометка удаления",
УровеньЖурналаРегистрации.Ошибка,
Выборка.Ссылка.Метаданные(),
Выборка.Ссылка,
ОписаниеОшибки());
КонецПопытки;
КонецЦикла;
Сообщить(СтрШаблон("Помечено: %1, пропущено: %2",
Формат(Помечено, "ЧГ=0"), Формат(Пропущено, "ЧГ=0")));
КонецПроцедуры
Что здесь важно:
- Условие
НЕ ПометкаУдаленияв запросе. Без него повторный запуск будет переписывать уже помеченные объекты и зря нагружать базу. Выборка, а неВыгрузить(). Выборка читает данные порциями, таблица значений грузит всё в память сервера.Заблокировать()перед изменением. Если элемент открыт у пользователя, метод вызовет исключение, и объект попадёт в «пропущенные», а не сломает цикл.- Своя
Попыткана каждый объект. Одна ошибка не останавливает обработку тысяч остальных.
Транзакция или нет
Для независимых объектов транзакция не нужна: каждый УстановитьПометкуУдаления пишется в своей. Общая транзакция нужна, когда пометка должна пройти «всё или ничего». Например, вы помечаете заказ и связанные с ним счета одной логической операцией.
НачатьТранзакцию();
Попытка
Для Каждого Ссылка Из СвязанныеДокументы Цикл
Ссылка.ПолучитьОбъект().УстановитьПометкуУдаления(Истина);
КонецЦикла;
ЗафиксироватьТранзакцию();
Исключение
ОтменитьТранзакцию();
ВызватьИсключение;
КонецПопытки;
Внутри транзакции не перехватывайте ошибку отдельного объекта и не продолжайте цикл. После исключения транзакция уже испорчена, и фиксация всё равно упадёт.
Большие объёмы (десятки тысяч объектов) режьте на порции по 500–1000 и запускайте в нерабочее время. Каждая пометка проведённого документа — это ещё и удаление движений, а значит, блокировки регистров.
Отбор кандидатов: временные таблицы и пакетный запрос
Частая задача — пометить то, что нигде не используется. Например, контрагентов из группы «Разовые», по которым нет ни поступлений, ни реализаций. Одним запросом с вложенными условиями это читается плохо. Пакетный запрос с временными таблицами разбивает отбор на понятные шаги.
Пример для Бухгалтерии 3.0 (имена документов замените на свои):
Запрос = Новый Запрос;
Запрос.МенеджерВременныхТаблиц = Новый МенеджерВременныхТаблиц;
Запрос.Текст =
"ВЫБРАТЬ
| Контрагенты.Ссылка КАК Ссылка
|ПОМЕСТИТЬ ВТ_Кандидаты
|ИЗ
| Справочник.Контрагенты КАК Контрагенты
|ГДЕ
| Контрагенты.Родитель = &Группа
| И НЕ Контрагенты.ПометкаУдаления
| И НЕ Контрагенты.ЭтоГруппа
|ИНДЕКСИРОВАТЬ ПО
| Ссылка
|;
|
|////////////////////////////////////////////////////////////
|ВЫБРАТЬ РАЗЛИЧНЫЕ
| Реализация.Контрагент КАК Контрагент
|ПОМЕСТИТЬ ВТ_Используемые
|ИЗ
| Документ.РеализацияТоваровУслуг КАК Реализация
|ГДЕ
| Реализация.Контрагент В
| (ВЫБРАТЬ ВТ_Кандидаты.Ссылка ИЗ ВТ_Кандидаты)
|
|ОБЪЕДИНИТЬ
|
|ВЫБРАТЬ
| Поступление.Контрагент
|ИЗ
| Документ.ПоступлениеТоваровУслуг КАК Поступление
|ГДЕ
| Поступление.Контрагент В
| (ВЫБРАТЬ ВТ_Кандидаты.Ссылка ИЗ ВТ_Кандидаты)
|
|ИНДЕКСИРОВАТЬ ПО
| Контрагент
|;
|
|////////////////////////////////////////////////////////////
|ВЫБРАТЬ
| ВТ_Кандидаты.Ссылка КАК Ссылка
|ИЗ
| ВТ_Кандидаты КАК ВТ_Кандидаты
| ЛЕВОЕ СОЕДИНЕНИЕ ВТ_Используемые КАК ВТ_Используемые
| ПО ВТ_Кандидаты.Ссылка = ВТ_Используемые.Контрагент
|ГДЕ
| ВТ_Используемые.Контрагент ЕСТЬ NULL";
Запрос.УстановитьПараметр("Группа", ГруппаРазовые);
Выборка = Запрос.Выполнить().Выбрать();
Дальше выборку обрабатывает тот же цикл с Попыткой, что и в предыдущем разделе.
Как это работает:
- 🧩 первая таблица — все кандидаты по условию;
- 🔗 вторая — те из них, что встречаются в документах (
ОБЪЕДИНИТЬбезВСЕубирает повторы); - 🎯 итог — кандидаты, которых нет во второй таблице.
ИНДЕКСИРОВАТЬ ПО ускоряет соединение, когда кандидатов тысячи. На сотне записей разницы не будет.
Запрос проверяет только те документы, которые вы перечислили. Контрагент может встречаться в договорах, банковских выписках, остатках на счетах. Окончательную проверку ссылок всё равно сделает платформа при удалении помеченных, так что ошибка здесь не испортит данные, но лишние объекты окажутся помеченными.
Вместо Выполнить() можно вызвать ВыполнитьПакет() и получить результаты всех запросов пакета массивом. Это удобно при отладке: видно, сколько записей попало в каждую временную таблицу.
Внешняя обработка для пометки на удаление: сборка и запуск
Код из примеров удобно оформить внешней обработкой (.epf), чтобы не трогать конфигурацию.
- В Конфигураторе:
Файл → Новый → Внешняя обработка. - Добавьте форму обработки, на форму — команду
Пометить. - В модуле формы создайте клиентский обработчик команды, который вызывает серверную процедуру с кодом пометки (
&НаСервере). - Если нужен выбор группы или периода, добавьте реквизиты формы и передайте их параметрами.
- Сохраните файл через
Файл → Сохранить как.
Запуск в пользовательском режиме: главное меню (кнопка с тремя полосками) → Файл → Открыть, выберите файл .epf. Для открытия нужно право на интерактивное открытие внешних обработок. Без него пункт недоступен или появится сообщение о нарушении прав.
Сначала запустите обработку в режиме «только посчитать»: добавьте флажок, при котором цикл выводит число найденных объектов и ничего не меняет. Прогон на копии базы покажет, сколько объектов будет затронуто, ещё до реальной пометки.
Если обработка должна работать у пользователя с урезанными правами, внутри серверной процедуры можно вызвать УстановитьПривилегированныйРежим(Истина). Делайте это только в обработке, которую контролирует администратор: привилегированный режим отключает проверку прав для всего кода после вызова.
Ошибки при программной пометке и как их обработать
Исключения при пометке дают три источника: права, блокировки и обработчики модуля объекта. Текст ошибки получайте через ОписаниеОшибки() внутри блока Исключение и пишите в журнал регистрации: так его увидит администратор, даже если обработку запускали ночью регламентным заданием.
Отдельно про ОбменДанными.Загрузка = Истина. Этот флаг перед пометкой отключает проверки в обработчиках модуля объекта. В обменах так и делают, но в своей обработке он скрывает бизнес-проверки, которые конфигурация добавила не просто так. Используйте его только когда точно знаете, какие обработчики пропускаете.
Удаление помеченных объектов после программной пометки
Пометка — первый шаг. Чтобы объекты исчезли из базы, их нужно удалить с проверкой ссылок.
Штатная обработка
В типовых конфигурациях на БСП (Бухгалтерия 3.0, ЗУП 3.1, УТ 11): Администрирование → Обслуживание, группа «Корректировка данных» → «Удаление помеченных объектов». Обработка покажет объекты, которые удалить нельзя, и где на них есть ссылки.
Универсальный путь на платформе: Все функции → Стандартные → Удаление помеченных объектов. Пункт «Все функции» включается в параметрах (главное меню → Сервис → Параметры).
Из кода
Найти все помеченные объекты базы:
Помеченные = НайтиПомеченныеНаУдаление();
Проверить ссылки и удалить только «свободные»:
Ссылки = НайтиПоСсылкам(Помеченные);
// Ссылки — таблица: Ссылка, Данные, Метаданные
Занятые = Ссылки.ВыгрузитьКолонку("Ссылка");
Для Каждого Ссылка Из Помеченные Цикл
Если Занятые.Найти(Ссылка) = Неопределено Тогда
Попытка
Ссылка.ПолучитьОбъект().Удалить();
Исключение
ЗаписьЖурналаРегистрации("Удаление помеченных",
УровеньЖурналаРегистрации.Ошибка, , Ссылка, ОписаниеОшибки());
КонецПопытки;
КонецЕсли;
КонецЦикла;
Колонка Ссылка в результате НайтиПоСсылкам — тот объект, на который ссылаются; колонка Данные — кто ссылается. Ссылка помеченного объекта на другой помеченный тоже считается: такие пары удаляются вместе штатной обработкой, которая учитывает это сама.
Короче — платформенный метод с контролем:
УдалитьОбъекты(Помеченные, Истина);
Второй параметр Истина включает проверку ссылочной целостности.
На большой базе НайтиПомеченныеНаУдаление() и НайтиПоСсылкам() просматривают все таблицы и выполняются долго. Перед удалением помеченных сделайте резервную копию базы и запускайте процедуру, когда пользователи не работают: физическое удаление не отменить.
Частые вопросы
Можно ли пометить на удаление запись регистра сведений?
Очистить() и Записать().Как программно снять пометку удаления со всех объектов справочника?
ПометкаУдаления = ИСТИНА и для каждого вызовите УстановитьПометкуУдаления(Ложь). Документы после снятия пометки остаются непроведёнными.Попадёт ли программная пометка в журнал регистрации?
ЗаписьЖурналаРегистрации.Уйдёт ли пометка в другую базу по обмену?
Почему пометка не ставится из клиентского кода?
&НаСервере или общий модуль с флагом «Сервер» и передавайте в неё ссылки.