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

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

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

Но как быть, если пользователь передумал вносить изменения и просто закрыл форму ввода причины (нажал на «крестик» или кнопку «Отмена»)? В этом случае система должна автоматически вернуть первоначальное значение количества, которое было в строке до начала редактирования. Давайте подробно разберем, как решить эту задачу в обычных и управляемых формах 1С, исправим частые ошибки и построим правильную архитектуру логирования данных.

Вариант 1. Базовое решение для обычных форм через «НачалоВыбора»

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

Разберем по шагам, как настроить это поведение:

  1. Устанавливаем у колонки «Количество» табличного поля свойство КнопкаСпискаВыбора или КнопкаВыбора в значение Истина.
  2. Создаем обработчик события НачалоВыбора для этого табличного поля.

Посмотрим на пример кода в модуле формы документа (исправленная и оптимизированная версия кода из темы форума):


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

Теперь проанализируем код кнопки «ОК» во вспомогательной форме ввода причины:


Процедура КнопкаОКНажатие(Элемент)
    Если ПустаяСтрока(ПричинаИзменения) Тогда
        Предупреждение("Необходимо обязательно указать причину изменения количества!");
        Возврат;
    КонецЕсли;
    
    // Закрываем форму и возвращаем введенное пользователем количество
    Закрыть(Количество);
КонецПроцедуры

Если же пользователь закроет форму крестиком или нажмет «Отмена», форма вернет значение Неопределено. Наше условие Если РезультатФормы <> Неопределено Тогда не выполнится, и количество в строке табличной части останется прежним. Задача решена!

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

Вариант 2. Улучшение UX: свободный ввод с клавиатуры и откат при изменении

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

Рассмотрим подробнее этот алгоритм:

  1. В начале модуля формы объявляем переменную СтароеКоличество, которая будет хранить значение до изменения.
  2. В событии ПередНачаломИзменения табличного поля фиксируем текущее значение ячейки.
  3. В событии ПриИзменении проверяем, поменялось ли число. Если поменялось — открываем форму ввода причины. При отказе возвращаем старое значение.

Реализуем это в коде модуля формы документа:


Перем СтароеКоличество;

Процедура ТоварыПередНачаломИзменения(Элемент, Отказ)
    ТекущиеДанные = Элемент.ТекущиеДанные;
    Если ТекущиеДанные <> Неопределено Тогда
        // Запоминаем значение в переменную модуля до начала редактирования
        СтароеКоличество = ТекущиеДанные.Количество;
    КонецЕсли;
КонецПроцедуры

Процедура ТоварыКоличествоПриИзменении(Элемент)
    ТекущиеДанные = ЭлементыФормы.Товары.ТекущиеДанные;
    Если ТекущиеДанные = Неопределено Тогда
        Возврат;
    КонецЕсли;
    
    // Если количество фактически не изменилось, ничего не делаем
    Если ТекущиеДанные.Количество = СтароеКоличество Тогда
        Возврат;
    КонецЕсли;
    
    // Открываем форму ввода причины изменения
    ФормаВвода = ПолучитьФорму("ОбщаяФорма.ВводПричиныИзменения");
    ФормаВвода.СтароеКоличество = СтароеКоличество;
    ФормаВвода.НовоеКоличество = ТекущиеДанные.Количество;
    
    Результат = ФормаВвода.ОткрытьМодально();
    
    Если Результат = Неопределено Тогда
        // Пользователь отменил ввод — возвращаем старое значение обратно в ячейку
        ТекущиеДанные.Количество = СтароеКоличество;
        Сообщить("Изменение отменено: не указана причина изменения количества.");
    Иначе
        // Причина указана успешно. Сохраняем её для последующей записи в лог
        ТекущиеДанные.ПричинаИзменения = Результат.Причина;
        ТекущиеДанные.КомментарийИзменения = Результат.Комментарий;
    EndIf;
КонецПроцедуры

Вариант 3. Важная архитектурная проблема: Транзакционность логирования

В первом сообщении темы автор пытается записать лог в регистр сведений ЛогРедактированияДокументов непосредственно в момент изменения ячейки на форме. Это грубейшая архитектурная ошибка!

Проанализируем ситуацию:

Правильное решение: Запись лога должна происходить строго в одной транзакции с проведением или записью самого документа. Для этого:

  1. Добавьте в табличную часть документа скрытые реквизиты (например, ПричинаИзменения).
  2. При успешном закрытии формы ввода причины записывайте её в текущую строку табличной части (как показано в Варианте 2).
  3. В модуле объекта документа в событии ПередЗаписью выполняйте сравнение табличной части записываемого объекта с данными этого же документа из базы данных (через Ссылка.Товары).
  4. Если обнаружено расхождение в количестве — формируйте движения по регистру сведений ЛогРедактированияДокументов. Если транзакция записи документа сорвется, то и записи в регистре не появятся.

Вариант 4. Реализация на Управляемых формах (без модальности)

Если вы работаете в современной конфигурации на управляемых формах 1С v8.3, использование метода ОткрытьМодально() запрещено из-за требований кроссплатформенности и работы в веб-клиенте. Нам необходимо использовать асинхронные вызовы и объект ОписаниеОповещенияОЗакрытии.

Посмотрим, как элегантно решить эту задачу на управляемых формах:

Код в модуле основной управляемой формы документа:


&НаКлиенте
Перем СтароеКоличество; // Переменная будет жить в контексте клиента во время редактирования

&НаКлиенте
Процедура ТоварыПриАктивизацииЯчейки(Элемент)
    ТекущиеДанные = Элементы.Товары.ТекущиеДанные;
    Если ТекущиеДанные <> Неопределено И Элемент.ТекущийЭлемент.Имя = "ТоварыКоличество" Тогда
        // Фиксируем значение при активизации ячейки количества
        СтароеКоличество = ТекущиеДанные.Количество;
    КонецЕсли;
КонецПроцедуры

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

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

Теперь посмотрим на код в управляемой форме ввода причины (ОбщаяФорма.ВводПричиныИзменения):


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

&НаКлиенте
Процедура КомандаОтмена(Команда)
    // Закрываем без передачи параметров
    Закрыть(Неопределено);
КонецПроцедуры

Подведем итоги

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

← На главную