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