Пометка удаления из кода: объект, форма, запрос и COM-скрипт

· 9 мин

Содержание

Чтобы программно установить пометку удаления в 1С, получите объект по ссылке и вызовите у него метод УстановитьПометкуУдаления(Истина): Ссылка.ПолучитьОбъект().УстановитьПометкуУдаления(Истина). Метод сам записывает объект, отдельный Записать() не нужен. Код должен выполняться на сервере: у ссылки этого метода нет, а на клиенте объект недоступен.

Частое заблуждение — что достаточно присвоить Объект.ПометкаУдаления = Истина и записать. Для справочника это иногда срабатывает, но для проведённого документа это неверный путь: метод УстановитьПометкуУдаления одновременно отменяет проведение и очищает движения, а ручное присваивание этого не гарантирует. Всё, что описано ниже, относится к платформе 1С:Предприятие 8.3 и не зависит от конфигурации: БП, ЗУП, УТ или своя разработка.

Как работает метод УстановитьПометкуУдаления

Метод есть у объектов всех ссылочных типов: справочников, документов, планов видов характеристик, планов счетов, планов видов расчёта, бизнес-процессов, задач и планов обмена. У регистров и констант пометки удаления нет вообще.

Сигнатура отличается по типам:

Тип объектаВызовЧто делает дополнительно
Справочник, ПВХ, план счетовУстановитьПометкуУдаления(Пометка, ВключаяПодчиненные)Для группы помечает вложенные элементы; второй параметр по умолчанию Истина
ДокументУстановитьПометкуУдаления(Пометка)Проведённый документ распроводится, движения удаляются
Прочие ссылочныеУстановитьПометкуУдаления(Пометка)Записывает объект с новым значением пометки

Что происходит при вызове:

  • 📝 объект записывается, срабатывают ПередЗаписью и ПриЗаписи модуля объекта и подписки на события;
  • 🔄 у проведённого документа выполняется отмена проведения, поэтому вызывается и ОбработкаУдаленияПроведения;
  • 🔒 перед записью платформа берёт блокировку объекта; если его держит открытая форма другого пользователя, запись упадёт;
  • ✅ проверяется право на изменение объекта. Права «Интерактивная пометка удаления» относятся к действиям пользователя в интерфейсе, на код они не влияют.

Простейший пример в серверном модуле:

Объект = Ссылка.ПолучитьОбъект();
Если Объект <> Неопределено И Не Объект.ПометкаУдаления Тогда
    Объект.УстановитьПометкуУдаления(Истина);
КонецЕсли;

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

Чтобы отследить момент пометки в модуле объекта, сравните текущее значение с сохранённым: Если ПометкаУдаления И Не Ссылка.Пустая() И Не Ссылка.ПометкаУдаления Тогда. Обращение Ссылка.ПометкаУдаления читает значение из базы, то есть до записи.

Пометка удаления из управляемой формы

В управляемом приложении код формы разделён на клиент и сервер. Кнопка нажимается на клиенте, а пометку ставят на сервере. Есть два типовых сценария: форма списка и форма самого объекта.

Форма списка: пометить выделенные строки

Добавьте в форму команду, привяжите её к кнопке в командной панели списка и напишите два обработчика:

&НаКлиенте
Процедура ПометитьВыделенные(Команда)
    Выделенные = Элементы.Список.ВыделенныеСтроки;
    Если Выделенные.Количество() = 0 Тогда
        Возврат;
    КонецЕсли;
    ПометитьНаСервере(Выделенные);
    Элементы.Список.Обновить();
КонецПроцедуры

&НаСервереБезКонтекста
Процедура ПометитьНаСервере(Ссылки)
    Для Каждого Ссылка Из Ссылки Цикл
        Объект = Ссылка.ПолучитьОбъект();
        Если Объект = Неопределено Или Объект.ПометкаУдаления Тогда
            Продолжить;
        КонецЕсли;
        Попытка
            Объект.Заблокировать();
            Объект.УстановитьПометкуУдаления(Истина);
        Исключение
            Сообщение = Новый СообщениеПользователю;
            Сообщение.Текст = СтрШаблон("Не удалось пометить %1: %2",
                Ссылка, КраткоеПредставлениеОшибки(ИнформацияОбОшибке()));
            Сообщение.Сообщить();
        КонецПопытки;
    КонецЦикла;
КонецПроцедуры

НаСервереБезКонтекста выбран намеренно: форму на сервер передавать не нужно, вызов получается быстрее. Заблокировать() заранее проверяет, не открыт ли объект у коллеги, и даёт понятную ошибку по конкретной строке, а не обрывает весь цикл.

Если пометку ставят из другой формы, а список открыт отдельно, после записи вызовите на клиенте ОповеститьОбИзменении(Ссылка): открытые списки этого типа перечитают данные.

Форма объекта: пометить открытый документ

В форме объекта данные живут в реквизите Объект. Его переводят в прикладной объект, ставят пометку и возвращают обратно в форму:

&НаСервере
Процедура ПереключитьПометкуНаСервере()
    ДокОбъект = РеквизитФормыВЗначение("Объект");
    ДокОбъект.УстановитьПометкуУдаления(Не ДокОбъект.ПометкаУдаления);
    ЗначениеВРеквизитФормы(ДокОбъект, "Объект");
КонецПроцедуры

Две оговорки по этому способу:

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

Стандартная команда «Пометить на удаление» в формах уже есть. Свою команду имеет смысл писать, когда нужна дополнительная логика: проверка статуса, запись причины в комментарий, запрет для отдельных ролей.

Массовая пометка на удаление запросом и выборкой

Типовая задача — пометить сотни объектов по условию: старые непроведённые черновики, номенклатуру из закрытой группы, договоры без движения. Схема одна: запросом получить ссылки, затем по каждой вызвать метод.

Отбор ссылок запросом

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

Условие НЕ ПометкаУдаления в отборе обязательно: без него повторный запуск будет заново записывать уже помеченные объекты.

Запись порциями в транзакциях

Одна транзакция на 50 000 объектов — плохая идея: она долго держит блокировки, раздувает журнал транзакций СУБД и при ошибке на последнем объекте откатывает всё. Разбейте массив на порции по 200–500 штук:

Процедура ПометитьПорцию(Порция)
    НачатьТранзакцию();
    Попытка
        Для Каждого Ссылка Из Порция Цикл
            Объект = Ссылка.ПолучитьОбъект();
            Объект.Заблокировать();
            Объект.УстановитьПометкуУдаления(Истина);
        КонецЦикла;
        ЗафиксироватьТранзакцию();
    Исключение
        ОтменитьТранзакцию();
        ВызватьИсключение;
    КонецПопытки;
КонецПроцедуры

РазмерПорции = 500;
Порция = Новый Массив;
Для Каждого Ссылка Из Ссылки Цикл
    Порция.Добавить(Ссылка);
    Если Порция.Количество() = РазмерПорции Тогда
        ПометитьПорцию(Порция);
        Порция = Новый Массив;
    КонецЕсли;
КонецЦикла;
Если Порция.Количество() > 0 Тогда
    ПометитьПорцию(Порция);
КонецЕсли;

Если порция упала, уже зафиксированные порции остаются помеченными. Повторный запуск с тем же запросом подберёт только оставшиеся объекты благодаря условию НЕ ПометкаУдаления.

Выборка через менеджер

Без запроса можно обойтись выборкой менеджера: Справочники.Номенклатура.Выбрать() или Документы.ПоступлениеТоваровУслуг.Выбрать(ДатаНачала, ДатаОкончания). У элемента выборки есть ПолучитьОбъект(). Удобно для простого перебора, но сложное условие (по реквизитам табличной части, по связанным данным) проще и быстрее описать запросом.

Выбор способа

СпособКогда подходитРиски
Цикл без транзакцииДо нескольких сотен объектов, ошибки по отдельным объектам допустимыЧасть объектов останется непомеченной, нужен лог
Порции в транзакцияхТысячи объектов, нужна целостность внутри порцииПадение порции откатывает 500 объектов, а не один
Одна транзакцияНабор связанных объектов, которые должны пометиться вместеДолгие блокировки, мешает работе пользователей
Фоновое заданиеДолгая обработка в рабочее времяОшибки видны только в журнале или в сообщениях задания

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

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

Пометка через COM-соединение из внешнего скрипта

Внешнее соединение позволяет поставить пометку без запуска клиента: из планировщика задач, из скрипта интеграции, из другой программы. Объект соединения — V83.COMConnector.

Пример на PowerShell для файловой базы:

$connector = New-Object -ComObject "V83.COMConnector"
$db = $connector.Connect('File="D:\Bases\Trade";Usr="Robot";Pwd="secret";')

$ref = $db.Catalogs.Номенклатура.FindByCode("00-00000123")
if (-not $ref.IsEmpty()) {
    $obj = $ref.GetObject()
    if (-not $obj.DeletionMark) {
        $obj.SetDeletionMark($true)
    }
}

[System.Runtime.InteropServices.Marshal]::ReleaseComObject($db) | Out-Null

Для клиент-серверной базы строка соединения выглядит так: Srvr="server1c";Ref="trade";Usr="Robot";Pwd="secret";.

Что проверить, чтобы скрипт заработал:

  • 🧩 компонента comcntr.dll зарегистрирована той же разрядности, что и процесс скрипта: 64-битный PowerShell работает только с 64-битной платформой. Регистрация от имени администратора: regsvr32 "C:\Program Files\1cv8\<версия>\bin\comcntr.dll";
  • 👤 у пользователя Robot есть право внешнего соединения и права на изменение нужных объектов;
  • 🎫 соединение — полноценный сеанс, он занимает клиентскую лицензию;
  • 🔑 пароль не храните в тексте скрипта: берите его из защищённого хранилища или переменной окружения.

Во внешнем соединении доступны и русские, и английские имена методов. Имена объектов метаданных (Номенклатура) остаются такими, как в конфигурации. Массовую логику лучше держать в общем модуле конфигурации с флагом «Внешнее соединение», а из скрипта вызывать одну экспортную процедуру. Так код хранится в базе, а не в разрозненных скриптах.

Альтернатива COM без лицензионных и разрядных сложностей на стороне скрипта — HTTP-сервис в конфигурации или стандартный интерфейс OData: там пометку меняют запросом на изменение поля DeletionMark.

Снятие пометки, удаление помеченных и возврат данных

Пометка — это ещё не удаление. Объект остаётся в базе, на него работают все ссылки, его можно вернуть.

Снять пометку программно

Тот же метод с параметром Ложь: Объект.УстановитьПометкуУдаления(Ложь). У справочника при ВключаяПодчиненные = Истина пометка снимется и с вложенных элементов группы.

Документ после снятия пометки сам не проводится. Его нужно провести заново: Объект.Записать(РежимЗаписиДокумента.Проведение). Если документов много, проводите их в хронологическом порядке, иначе остатки на промежуточные даты разойдутся.

Физическое удаление помеченных

Окончательно помеченные объекты удаляет обработка «Удаление помеченных объектов». В типовых конфигурациях на БСП она находится в разделе Администрирование → Обслуживание. Обработка проверяет ссылки: объект, на который ссылаются другие данные, не удаляется, пока ссылку не уберут.

Программно эту же работу делают функции глобального контекста:

Помеченные = НайтиПомеченныеНаУдаление();
Ссылающиеся = НайтиПоСсылкам(Помеченные);
// Ссылающиеся — таблица значений: Ссылка, Данные, Метаданные

НайтиПоСсылкам показывает, кто ссылается на помеченные объекты. Это удобно, чтобы до удаления понять, почему, например, контрагент «не удаляется».

У объекта есть метод Удалить(), который стирает запись сразу, без пометки и без контроля ссылок. После него в документах остаются битые ссылки вида «<Объект не найден>». Для рабочих данных используйте пометку и обработку удаления, а Удалить() оставьте для служебных объектов, на которые точно никто не ссылается.

Если удалили лишнее

После физического удаления объект средствами 1С не восстановить. Единственный путь — резервная копия: разверните её рядом, найдите объект и перенесите его (выгрузкой в XML, обработкой переноса данных или вручную), а не загружайте копию поверх рабочей базы с потерей свежих данных.

Ошибки при программной пометке и как их разобрать

Диагностика: выберите симптом

Про обход проверок отдельно. Установка ОбменДанными.Загрузка = Истина перед записью отключает выполнение прикладной логики в обработчиках модуля объекта. Этот режим придуман для обменов данными. Использовать его, чтобы «пробить» пометку через проверки конфигурации, опасно: вы обходите и ту логику, которая поддерживает целостность учёта.

Частые вопросы

Можно ли пометить на удаление запись регистра сведений?
Нет. У регистров нет пометки удаления. Записи независимого регистра удаляют через набор записей: устанавливают отбор, очищают набор и записывают его пустым.
Нужен ли монопольный режим для программной пометки?
Нет. Пометка — обычная запись объекта и работает при подключённых пользователях. Мешать могут только блокировки конкретных объектов, открытых у коллег.
Как найти все объекты, помеченные на удаление, кодом?
Функция глобального контекста НайтиПомеченныеНаУдаление() возвращает массив ссылок по всей базе. Для одного вида объектов быстрее запрос с условием ГДЕ ПометкаУдаления.
Сохраняется ли в журнале регистрации, кто поставил пометку из кода?
Да. Запись объекта фиксируется в журнале регистрации от имени пользователя сеанса, в том числе для фоновых заданий и COM-соединения. Поэтому для скриптов удобно заводить отдельного пользователя, например Robot.
Помечаются ли подчинённые справочники вместе с владельцем?
При пометке элемента-владельца платформа помечает и элементы подчинённых ему справочников, например договоры контрагента. При снятии пометки с владельца проверьте подчинённые элементы отдельно.