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

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

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

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

Типичные ошибки разработчиков при удалении записей

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

  1. Опасность метода Очистить() у набора записей: Вызов метода НаборЗаписей.Очистить() полностью опустошает коллекцию в оперативной памяти. Если после этого записать набор в базу данных, система удалит абсолютно все движения по данному регистратору (или по текущему отбору), а не только одну выбранную пользователем строку. Это приводит к катастрофической потере данных.
  2. Попытка использовать МенеджерЗаписи для подчиненных регистров: Платформа 1С накладывает жесткое ограничение: для регистров, подчиненных регистратору, запись и удаление возможны только через объект НаборЗаписей с обязательным установлением отбора по регистратору. Объект МенеджерЗаписи для таких регистров использовать запрещено программно.
  3. Смещение индексов при обходе коллекции в цикле: Если обходить набор записей с начала к концу (индексы от 0 до N) и удалять строки по условию, то после удаления первой подходящей строки все последующие элементы смещаются на один индекс назад. В результате цикл пропускает следующую за удаленной строку. Обход коллекции всегда нужно выполнять с конца к началу.

Теперь, когда мы понимаем зоны риска, рассмотрим правильные подходы к реализации функционала.

Концепция работы с выделенными строками на Управляемой форме

В управляемом интерфейсе, когда табличная часть формы отображает динамический список регистра сведений, свойство элемента формы Элементы.ЗаписиРегистра.ВыделенныеСтроки возвращает массив элементов специального типа — РегистрСведенийКлючЗаписи.<ИмяРегистра>.

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

Решение для независимого регистра сведений

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

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


&НаКлиенте
Процедура УдалитьВыделенныеСтрокиНезависимого(Команда)
    
    ВыделенныеСтроки = Элементы.ЗаписиРегистра.ВыделенныеСтроки;
    
    Если ВыделенныеСтроки.Количество() > 0 Тогда
        // Вызываем серверную процедуру без контекста формы для лучшей производительности
        УдалитьСтрокиНезависимогоНаСервере(ВыделенныеСтроки);
        // Обновляем динамический список на клиенте, чтобы удаленные строки исчезли с экрана
        Элементы.ЗаписиРегистра.Обновить();
    Иначе
        ПоказатьПредупреждение(, "Не выбрано ни одной строки для удаления!");
    КонецЕсли;
    
КонецПроцедуры

&НаСервереБезКонтекста
Процедура УдалитьСтрокиНезависимогоНаСервере(МассивКлючейЗаписей)
    
    Для Каждого КлючЗаписи Из МассивКлючейЗаписей Цикл
        // Создаем менеджер записи для конкретного регистра
        МенеджерЗаписи = РегистрыСведений.ИмяРегистра.СоздатьМенеджерЗаписи();
        
        // Копируем измерения и период из ключа в менеджер записи
        ЗаполнитьЗначенияСвойств(МенеджерЗаписи, КлючЗаписи);
        
        // Проверяем существование записи в базе данных перед удалением
        МенеджерЗаписи.Прочитать();
        Если МенеджерЗаписи.Выбран() Тогда
            МенеджерЗаписи.Удалить();
        КонецЕсли;
    КонецЦикла;
    
КонецПроцедуры

Этот алгоритм работает быстро, безопасно и не перегружает сервер контекстными вызовами.

Решение для регистра, подчиненного регистратору

Удаление записей из подчиненного регистра (например, такого как КадроваяИсторияСотрудников в ЗУП) требует деликатного подхода. Платформа не позволяет удалить одну строку напрямую. Нам необходимо выполнить следующие шаги:

  1. Сгруппировать переданные ключи записей по документам-регистраторам.
  2. Для каждого уникального регистратора прочитать весь существующий набор движений в память сервера.
  3. Удалить из прочитанного набора именно те строки, которые соответствуют выделенным пользователем ключам (используя безопасный обход с конца коллекции).
  4. Записать измененный набор обратно в базу данных с флагом замещения.

Давайте посмотрим, как выглядит правильная программная реализация этой логики:


&НаКлиенте
Процедура УдалитьВыделенныеСтрокиПодчиненного(Команда)
    
    ВыделенныеСтроки = Элементы.ЗаписиРегистра.ВыделенныеСтроки;
    
    Если ВыделенныеСтроки.Количество() > 0 Тогда
        УдалитьСтрокиПодчиненногоНаСервере(ВыделенныеСтроки);
        Элементы.ЗаписиРегистра.Обновить();
    Иначе
        ПоказатьПредупреждение(, "Не выбрано ни одной строки!");
    КонецЕсли;
    
КонецПроцедуры

&НаСервереБезКонтекста
Процедура УдалитьСтрокиПодчиненногоНаСервере(МассивКлючейЗаписей)
    
    // Шаг 1. Группируем ключи удаляемых записей по Регистраторам
    // Это спасет систему от избыточных чтений и записей СУБД, если выделено много строк одного документа
    ЗаписиПоРегистраторам = Новый Соответствие; 
    
    Для Каждого КлючЗаписи Из МассивКлючейЗаписей Цикл
        ТекущийРегистратор = КлючЗаписи.Регистратор;
        
        Если ЗаписиПоРегистраторам[ТекущийРегистратор] = Неопределено Тогда
            ЗаписиПоРегистраторам.Вставить(ТекущийРегистратор, Новый Массив);
        КонецЕсли;
        
        ЗаписиПоРегистраторам[ТекущийРегистратор].Добавить(КлючЗаписи);
    КонецЦикла;
    
    // Шаг 2. Обрабатываем каждый регистратор в отдельной транзакции записи набора
    Для Каждого ЭлементСоответствия Из ЗаписиПоРегистраторам Цикл
        
        Регистратор = ЭлементСоответствия.Ключ;
        КлючиДляУдаления = ЭлементСоответствия.Значение;
        
        // Создаем набор записей с жестким отбором по документу
        НаборЗаписей = РегистрыСведений.КадроваяИсторияСотрудников.СоздатьНаборЗаписей();
        НаборЗаписей.Отбор.Регистратор.Установить(Регистратор);
        НаборЗаписей.Прочитать();
        
        // Шаг 3. Обходим набор с конца по индексу во избежание сдвига элементов
        Индекс = НаборЗаписей.Количество() - 1;
        Пока Индекс >= 0 Цикл
            СтрокаНабора = НаборЗаписей[Индекс];
            
            // Проверяем, совпадает ли строка набора с одной из удаляемых строк
            Для Каждого Ключ Из КлючиДляУдаления Цикл
                // Сравниваем период и все ключевые измерения (например, Сотрудник и Организация)
                Если СтрокаНабора.Период = Ключ.Период 
                   И СтрокаНабора.Сотрудник = Ключ.Сотрудник Тогда
                    
                    // Удаляем конкретную строку из коллекции
                    НаборЗаписей.Удалить(Индекс);
                    Прервать; // Переходим к анализу следующей строки набора
                КонецЕсли;
            КонецЦикла;
            
            Индекс = Index - 1;
        КонецЦикла;
        
        // Шаг 4. Записываем измененный набор обратно в СУБД с замещением (Истина)
        Попытка
            НаборЗаписей.Записать(Истина);
        Исключение
            ЗаписьЖурналаРегистрации("Программное удаление записей регистра",
                УровеньЖурналаРегистрации.Ошибка,,, 
                ПодробноеПредставлениеОшибки(ИнформацияОбОшибке()));
            ВызватьИсключение "Не удалось записать изменения по документу " + СокрЛП(Регистратор);
        КонецПопытки;
        
    КонецЦикла;
    
КонецПроцедуры

Методологические риски прямой корректировки движений

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

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

← На главную