При проектировании интерфейсов в «1С:Предприятии» часто возникает задача: перед удалением строки из табличной части документа или обработки запросить у пользователя дополнительную информацию (например, причину удаления, комментарий или подтверждение) и записать эти сведения в регистр логов. Однако начинающие разработчики часто сталкиваются с логической ошибкой: если пользователь передумал удалять строку и просто закрыл открывшуюся форму (нажал на крестик, кнопку «Отмена» или клавишу Esc), строка из таблицы все равно пропадает.
В этой статье мы подробно разберем, почему стандартные подходы с модальными окнами могут давать сбой, проанализируем ключевые архитектурные ошибки и реализуем правильные механизмы отмены удаления для обычных и управляемых форм.
Прежде чем перейти к правильным решениям, разберем типичные ошибки, которые совершают разработчики при реализации этой задачи (именно они обсуждались в сообщениях темы форума):
ДокументОбъект (основного реквизита формы документа) во вспомогательную форму лога для непосредственного удаления строки — грубое нарушение объектной модели 1С. Если изменить и записать документ в форме лога, а затем продолжить работу в основной форме, платформа заблокирует запись основного документа из-за конфликта версий данных («Данные изменены или удалены другим пользователем»).Удалить(). Метод табличной части Удалить(Параметр) принимает в качестве параметра либо целочисленный индекс строки, либо сам объект строки табличной части. Передача туда уникального идентификатора или стороннего ключа связи приводит к системной ошибке: Ошибка при вызове метода контекста (Удалить): Несоответствие типов (параметр номер '1').ОткрытьФормуМодально() уходят в прошлое. Современные стандарты разработки «1С:Совместимо» требуют полного отказа от модальных окон в пользу асинхронных вызовов, так как модальность не поддерживается в веб-клиенте на мобильных устройствах и некоторых браузерах.Правильная стратегия: в обработчике события перед удалением строки мы должны безусловно отменить стандартное действие платформы, установить признак Отказ = Истина, открыть нашу вспомогательную форму и перенести всю логику удаления и записи в лог в контекст основной формы, выполняя удаление программно и только при успешном нажатии кнопки «ОК».
Рассмотрим по шагам реализацию этой задачи на платформе «1С:Предприятие 8.3» с использованием обычных форм. Мы организуем безопасное взаимодействие между формой документа и вспомогательной формой лога без передачи тяжелых объектных ссылок.
В модуле формы документа найдем или создадим обработчик события ПередУдалением для нашей табличной части (например, Товары). Нам необходимо полностью заблокировать стандартное удаление строки и вызвать нашу форму ввода причины удаления.
Процедура ТоварыПередУдалением(Элемент, Отказ)
// 1. Безусловно отменяем стандартное интерактивное удаление строки платформой
Отказ = Истина;
// 2. Получаем текущую строку, которую пользователь пытается удалить
ТекущаяСтрока = Элемент.ТекущаяСтрока;
Если ТекущаяСтрока = Неопределено Тогда
Возврат;
КонецЕсли;
// 3. Получаем форму лога из регистра сведений.
// Вторым параметром передаем владельца формы (ЭтаФорма), чтобы сработал механизм ОбработкиВыбора
ФормаЛога = РегистрыСведений.ЛогРедактированияДокументов.ПолучитьФорму("РеализацияПриУдалении", ЭтаФорма);
// 4. Передаем саму строку во вспомогательную форму лога, записав её в созданный реквизит этой формы
ФормаЛога.УдаляемаяСтрока = ТекущаяСтрока;
// Наполняем форму лога другими необходимыми контекстными данными
ФормаЛога.Было = ТекущаяСтрока.Количество;
// 5. Открываем форму ввода причины удаления
ФормаЛога.Открыть();
КонецПроцедуры
Во вспомогательной форме добавьте реквизит УдаляемаяСтрока (тип: Произвольный). При нажатии на кнопку «ОК» мы упаковываем все введенные пользователем данные в структуру и передаем их владельцу формы через метод ОповеститьОВыборе(). Если пользователь закроет форму крестиком, этот метод не вызовется, и строка останется нетронутой.
Процедура ОКНажатие(Элемент)
// Формируем структуру с данными, которые нам понадобятся для записи лога и удаления
ПараметрыВыбора = Новый Структура;
ПараметрыВыбора.Вставить("Действие", "УдалитьСтрокуСЛогированием");
ПараметрыВыбора.Вставить("СтрокаДляУдаления", УдаляемаяСтрока);
ПараметрыВыбора.Вставить("ПричинаИзменения", ПричинаИзменения);
ПараметрыВыбора.Вставить("Комментарий", Комментарий);
// Оповещаем владельца формы (документ) о совершенном выборе
ОповеститьОВыборе(ПараметрыВыбора);
// Закрываем форму лога
Закрыть();
КонецПроцедуры
В модуле формы документа переопределим или добавим стандартный обработчик события ОбработкаВыбора. Сюда автоматически придут данные после вызова ОповеститьОВыборе() из подчиненной формы.
Процедура ОбработкаВыбора(ЗначениеВыбора, Источник)
// Проверяем, что к нам пришла именно наша структура с указанием действия
Если ТипЗнч(ЗначениеВыбора) = Тип("Структура")
И ЗначениеВыбора.Свойство("Действие")
И ЗначениеВыбора.Действие = "УдалитьСтрокуСЛогированием" Тогда
СтрокаТЧ = ЗначениеВыбора.СтрокаДляУдаления;
// Вызываем процедуру записи лога в регистр сведений
ЗаписатьЛогВРегистрСведений(СтрокаТЧ, ЗначениеВыбора.ПричинаИзменения, ЗначениеВыбора.Комментарий);
// Программно удаляем строку из табличной части в контексте документа
Товары.Удалить(СтрокаТЧ);
КонецЕсли;
КонецПроцедуры
Для записи лога используем стандартный набор записей с отбором, чтобы гарантировать корректность записи данных в базу данных:
Процедура ЗаписатьЛогВРегистрСведений(СтрокаТЧ, Причина, Комментарий)
НаборЗаписей = РегистрыСведений.ЛогРедактированияДокументов.СоздатьНаборЗаписей();
// Настраиваем отбор по периоду и документу (если применимо)
НаборЗаписей.Отбор.Период.Установить(ТекущаяДата());
НоваяЗапись = НаборЗаписей.Добавить();
НоваяЗапись.Период = ТекущаяДата();
НоваяЗапись.Документ = Ссылка;
НоваяЗапись.Товар = СтрокаТЧ.Номенклатура;
НоваяЗапись.Пользователь = ПараметрыСеанса.ТекущийПользователь;
НоваяЗапись.Было = СтрокаТЧ.Количество;
НоваяЗапись.Стало = 0;
НоваяЗапись.ПричинаИзменения = Причина;
НоваяЗапись.Комментарий = Комментарий;
НаборЗаписей.Записать(Ложь);
КонецПроцедуры
Если ваша конфигурация работает на управляемом приложении (или планирует переход на него), использование синхронных вызовов и передача объектов строк между клиентом и сервером недопустимы. В управляемых формах Элементы.Товары.ТекущаяСтрока — это числовой идентификатор строки, а не сама строка.
Реализуем асинхронный паттерн с использованием объекта ОписаниеОповещения.
В модуле управляемой формы документа пропишем обработчик удаления строки:
&НаКлиенте
Процедура ТоварыПередУдалением(Элемент, Отказ)
Отказ = Истина; // Отменяем интерактивное удаление на клиенте
ИдентификаторСтроки = Элемент.ТекущаяСтрока;
Если ИдентификаторСтроки = Неопределено Тогда
Возврат;
КонецЕсли;
// Подготавливаем параметры для открытия формы лога
ПараметрыОткрытия = Новый Структура;
ПараметрыОткрытия.Вставить("ИдентификаторСтроки", ИдентификаторСтроки);
// Создаем описание оповещения для обработки закрытия формы
ОписаниеОповещения = Новый ОписаниеОповещения("ПослеЗакрытияФормыЛога", ЭтотОбъект, ПараметрыОткрытия);
// Открываем форму лога асинхронно
ОткрытьФорму("РегистрСведений.ЛогРедактированияДокументов.Форма.РеализацияПриУдалении",
ПараметрыОткрытия,
ЭтотОбъект,
,,,,
ОписаниеОповещения);
КонецПроцедуры
При нажатии на кнопку «ОК» в форме лога мы возвращаем заполненную структуру в метод Закрыть():
&НаКлиенте
Процедура ЗаписатьИЗакрыть(Команда)
РезультатВыбора = Новый Структура;
РезультатВыбора.Вставить("ПричинаИзменения", Объект.ПричинаИзменения);
РезультатВыбора.Вставить("Комментарий", Объект.Комментарий);
// Передаем заполненные данные обратно в обработчик оповещения
Закрыть(РезультатВыбора);
КонецПроцедуры
Возвращаемся в модуль формы документа и описываем процедуру обратного вызова, а также серверный контекст для безопасной работы с базой данных:
&НаКлиенте
Процедура ПослеЗакрытияФормыЛога(Результат, ДополнительныеПараметры) Экспорт
// Если Результат равен Неопределено, значит пользователь закрыл форму крестиком или нажал Отмену
Если Результат = Неопределено Тогда
Возврат;
КонецЕсли;
// Передаем управление на сервер для записи лога и модификации табличной части
УдалитьСтрокуИЗаписатьЛогНаСервере(ДополнительныеПараметры.ИдентификаторСтроки,
Результат.ПричинаИзменения,
Результат.Комментарий);
КонецПроцедуры
&НаСервере
Процедура УдалитьСтрокуИЗаписатьЛогНаСервере(ИдентификаторСтроки, Причина, Комментарий)
// Находим строку по её уникальному числовому идентификатору
СтрокаТЧ = Объект.Товары.НайтиПоИдентификатору(ИдентификаторСтроки);
Если СтрокаТЧ <> Неопределено Тогда
// 1. Записываем информацию в регистр сведений
НаборЗаписей = РегистрыСведений.ЛогРедактированияДокументов.СоздатьНаборЗаписей();
НаборЗаписей.Отбор.Период.Установить(ТекущаяДата());
НоваяЗапись = НаборЗаписей.Добавить();
НоваяЗапись.Период = ТекущаяДата();
НоваяЗапись.Документ = Объект.Ссылка;
НоваяЗапись.Товар = СтрокаТЧ.Номенклатура;
НоваяЗапись.Пользователь = ПараметрыСеанса.ТекущийПользователь;
НоваяЗапись.Было = СтрокаТЧ.Количество;
НоваяЗапись.Стало = 0;
НоваяЗапись.ПричинаИзменения = Причина;
НоваяЗапись.Комментарий = Комментарий;
НаборЗаписей.Записать();
// 2. Программно удаляем строку из табличной части на сервере
Объект.Товары.Удалить(СтрокаТЧ);
КонецЕсли;
КонецПроцедуры
Данный подход гарантирует высокую отказоустойчивость, исключает блокировки объектов базы данных и полностью соответствует актуальным требованиям к разработке в среде «1С:Предприятие 8».