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

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

При проектировании интерфейсов в «1С:Предприятии» часто возникает задача: перед удалением строки из табличной части документа или обработки запросить у пользователя дополнительную информацию (например, причину удаления, комментарий или подтверждение) и записать эти сведения в регистр логов. Однако начинающие разработчики часто сталкиваются с логической ошибкой: если пользователь передумал удалять строку и просто закрыл открывшуюся форму (нажал на крестик, кнопку «Отмена» или клавишу Esc), строка из таблицы все равно пропадает.

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

Почему стандартный механизм и простая передача «ДокументОбъект» не работают?

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

  1. Попытка изменения документа из сторонней формы лога. Передача ДокументОбъект (основного реквизита формы документа) во вспомогательную форму лога для непосредственного удаления строки — грубое нарушение объектной модели 1С. Если изменить и записать документ в форме лога, а затем продолжить работу в основной форме, платформа заблокирует запись основного документа из-за конфликта версий данных («Данные изменены или удалены другим пользователем»).
  2. Несоответствие типов в методе Удалить(). Метод табличной части Удалить(Параметр) принимает в качестве параметра либо целочисленный индекс строки, либо сам объект строки табличной части. Передача туда уникального идентификатора или стороннего ключа связи приводит к системной ошибке: Ошибка при вызове метода контекста (Удалить): Несоответствие типов (параметр номер '1').
  3. Проблемы с модальностью. Попытки использовать метод ОткрытьФормуМодально() уходят в прошлое. Современные стандарты разработки «1С:Совместимо» требуют полного отказа от модальных окон в пользу асинхронных вызовов, так как модальность не поддерживается в веб-клиенте на мобильных устройствах и некоторых браузерах.

Правильная стратегия: в обработчике события перед удалением строки мы должны безусловно отменить стандартное действие платформы, установить признак Отказ = Истина, открыть нашу вспомогательную форму и перенести всю логику удаления и записи в лог в контекст основной формы, выполняя удаление программно и только при успешном нажатии кнопки «ОК».


Решение для обычных форм (через обработку выбора)

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

Шаг 1. Перехват события удаления в форме документа

В модуле формы документа найдем или создадим обработчик события ПередУдалением для нашей табличной части (например, Товары). Нам необходимо полностью заблокировать стандартное удаление строки и вызвать нашу форму ввода причины удаления.


Процедура ТоварыПередУдалением(Элемент, Отказ)
    
    // 1. Безусловно отменяем стандартное интерактивное удаление строки платформой
    Отказ = Истина;
    
    // 2. Получаем текущую строку, которую пользователь пытается удалить
    ТекущаяСтрока = Элемент.ТекущаяСтрока;
    Если ТекущаяСтрока = Неопределено Тогда 
        Возврат; 
    КонецЕсли;
    
    // 3. Получаем форму лога из регистра сведений. 
    // Вторым параметром передаем владельца формы (ЭтаФорма), чтобы сработал механизм ОбработкиВыбора
    ФормаЛога = РегистрыСведений.ЛогРедактированияДокументов.ПолучитьФорму("РеализацияПриУдалении", ЭтаФорма);
    
    // 4. Передаем саму строку во вспомогательную форму лога, записав её в созданный реквизит этой формы
    ФормаЛога.УдаляемаяСтрока = ТекущаяСтрока;
    
    // Наполняем форму лога другими необходимыми контекстными данными
    ФормаЛога.Было = ТекущаяСтрока.Количество;
    
    // 5. Открываем форму ввода причины удаления
    ФормаЛога.Открыть();

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

Шаг 2. Код во вспомогательной форме лога (РеализацияПриУдалении)

Во вспомогательной форме добавьте реквизит УдаляемаяСтрока (тип: Произвольный). При нажатии на кнопку «ОК» мы упаковываем все введенные пользователем данные в структуру и передаем их владельцу формы через метод ОповеститьОВыборе(). Если пользователь закроет форму крестиком, этот метод не вызовется, и строка останется нетронутой.


Процедура ОКНажатие(Элемент)
    
    // Формируем структуру с данными, которые нам понадобятся для записи лога и удаления
    ПараметрыВыбора = Новый Структура;
    ПараметрыВыбора.Вставить("Действие", "УдалитьСтрокуСЛогированием");
    ПараметрыВыбора.Вставить("СтрокаДляУдаления", УдаляемаяСтрока);
    ПараметрыВыбора.Вставить("ПричинаИзменения", ПричинаИзменения);
    ПараметрыВыбора.Вставить("Комментарий", Комментарий);
    
    // Оповещаем владельца формы (документ) о совершенном выборе
    ОповеститьОВыборе(ПараметрыВыбора);
    
    // Закрываем форму лога
    Закрыть();

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

Шаг 3. Обработка результата в форме документа

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


Процедура ОбработкаВыбора(ЗначениеВыбора, Источник)
    
    // Проверяем, что к нам пришла именно наша структура с указанием действия
    Если ТипЗнч(ЗначениеВыбора) = Тип("Структура") 
       И ЗначениеВыбора.Свойство("Действие") 
       И ЗначениеВыбора.Действие = "УдалитьСтрокуСЛогированием" Тогда
        
        СтрокаТЧ = ЗначениеВыбора.СтрокаДляУдаления;
        
        // Вызываем процедуру записи лога в регистр сведений
        ЗаписатьЛогВРегистрСведений(СтрокаТЧ, ЗначениеВыбора.ПричинаИзменения, ЗначениеВыбора.Комментарий);
        
        // Программно удаляем строку из табличной части в контексте документа
        Товары.Удалить(СтрокаТЧ);
        
    КонецЕсли;

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

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


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

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

Решение для управляемых форм (асинхронный подход)

Если ваша конфигурация работает на управляемом приложении (или планирует переход на него), использование синхронных вызовов и передача объектов строк между клиентом и сервером недопустимы. В управляемых формах Элементы.Товары.ТекущаяСтрока — это числовой идентификатор строки, а не сама строка.

Реализуем асинхронный паттерн с использованием объекта ОписаниеОповещения.

Шаг 1. Перехват удаления на клиенте в управляемой форме

В модуле управляемой формы документа пропишем обработчик удаления строки:


&НаКлиенте
Процедура ТоварыПередУдалением(Элемент, Отказ)
    
    Отказ = Истина; // Отменяем интерактивное удаление на клиенте
    
    ИдентификаторСтроки = Элемент.ТекущаяСтрока;
    Если ИдентификаторСтроки = Неопределено Тогда 
        Возврат; 
    КонецЕсли;
    
    // Подготавливаем параметры для открытия формы лога
    ПараметрыОткрытия = Новый Структура;
    ПараметрыОткрытия.Вставить("ИдентификаторСтроки", ИдентификаторСтроки);
    
    // Создаем описание оповещения для обработки закрытия формы
    ОписаниеОповещения = Новый ОписаниеОповещения("ПослеЗакрытияФормыЛога", ЭтотОбъект, ПараметрыОткрытия);
    
    // Открываем форму лога асинхронно
    ОткрытьФорму("РегистрСведений.ЛогРедактированияДокументов.Форма.РеализацияПриУдалении", 
                 ПараметрыОткрытия, 
                 ЭтотОбъект, 
                 ,,,, 
                 ОписаниеОповещения);

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

Шаг 2. Код закрытия в управляемой форме лога

При нажатии на кнопку «ОК» в форме лога мы возвращаем заполненную структуру в метод Закрыть():


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

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

Шаг 3. Обработка результата и удаление строки на сервере

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


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

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

&НаСервере
Процедура УдалитьСтрокуИЗаписатьЛогНаСервере(ИдентификаторСтроки, Причина, Комментарий)
    
    // Находим строку по её уникальному числовому идентификатору
    СтрокаТЧ = Объект.Товары.НайтиПоИдентификатору(ИдентификаторСтроки);
    
    Если СтрокаТЧ <> Неопределено Тогда
        
        // 1. Записываем информацию в регистр сведений
        НаборЗаписей = РегистрыСведений.ЛогРедактированияДокументов.СоздатьНаборЗаписей();
        НаборЗаписей.Отбор.Период.Установить(ТекущаяДата());
        
        НоваяЗапись = НаборЗаписей.Добавить();
        НоваяЗапись.Период = ТекущаяДата();
        НоваяЗапись.Документ = Объект.Ссылка;
        НоваяЗапись.Товар = СтрокаТЧ.Номенклатура;
        НоваяЗапись.Пользователь = ПараметрыСеанса.ТекущийПользователь;
        НоваяЗапись.Было = СтрокаТЧ.Количество;
        НоваяЗапись.Стало = 0;
        НоваяЗапись.ПричинаИзменения = Причина;
        НоваяЗапись.Комментарий = Комментарий;
        
        НаборЗаписей.Записать();
        
        // 2. Программно удаляем строку из табличной части на сервере
        Объект.Товары.Удалить(СтрокаТЧ);
        
    КонецЕсли;

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

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

← На главную