В процессе эксплуатации информационных систем на базе 1С часто возникает задача контроля действий пользователей. Особенно это актуально для документов отгрузки, таких как РеализацияТоваровУслуг, где изменение количества или удаление строк задним числом может привести к расхождениям в учете. Рассмотрим подробнее, как реализовать механизм, который будет отслеживать изменения в табличной части, запрашивать у пользователя причину и фиксировать всё это в специальном регистре сведений.
Существует два основных подхода к решению этой задачи. Первый — отлавливать события ПриИзменении в каждой колонке и событие ПередУдалением в табличной части. Этот путь трудозатратен и сложен в поддержке. Мы разберем второй, более элегантный подход: сравнение состояния документа непосредственно перед его записью. Это позволит нам один раз проанализировать все изменения и выдать пользователю единое окно для указания причины.
Прежде чем приступать к кодированию, нам необходимо подготовить базу. Проанализируем, какие объекты нам понадобятся:
Документ (Тип: ДокументСсылка), ДатаИзменения (Тип: Дата), Пользователь (Тип: СправочникСсылка.Пользователи).Причина (Тип: СправочникСсылка.ПричиныИзменений), ОписаниеИзменений (Тип: Строка).Для того чтобы понять, что именно изменил пользователь, нам нужно сравнить данные в оперативной памяти (объект в форме) с данными, которые уже записаны в базе данных (старая копия). Разберем пример кода, который выполняет это сравнение с помощью запроса.
В процедуре на сервере мы выгрузим текущую табличную часть в ТаблицаЗначений и передадим её в запрос в качестве параметра. Посмотрим на реализацию логики сравнения:
&НаСервере
Функция ПолучитьТекстРазличий()
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ
| ТЗ_Товары.Номенклатура КАК Номенклатура,
| ТЗ_Товары.Количество КАК Количество
|ПОМЕСТИТЬ ВТ_ТекущиеТовары
|ИЗ
| &ТЗ_Товары КАК ТЗ_Товары
|;
|
|////////////////////////////////////////////////////////////////////////////////
|ВЫБРАТЬ
| ""Добавлено/Изменено"" КАК Статус,
| ВТ.Номенклатура КАК Номенклатура,
| ВТ.Количество КАК Количество
|ИЗ
| ВТ_ТекущиеТовары КАК ВТ
| ЛЕВОЕ СОЕДИНЕНИЕ Документ.РеализацияТоваровУслуг.Товары КАК РеализацияТовары
| ПО ВТ.Номенклатура = РеализацияТовары.Номенклатура
| И РеализацияТовары.Ссылка = &Ссылка
|ГДЕ
| РеализацияТовары.Номенклатура ЕСТЬ NULL
| ИЛИ ВТ.Количество <> РеализацияТовары.Количество
|
|ОБЪЕДИНИТЬ ВСЕ
|
|ВЫБРАТЬ
| ""Удалено"",
| РеализацияТовары.Номенклатура,
| РеализацияТовары.Количество
|ИЗ
| Документ.РеализацияТоваровУслуг.Товары КАК РеализацияТовары
| ЛЕВОЕ СОЕДИНЕНИЕ ВТ_ТекущиеТовары КАК ВТ
| ПО РеализацияТовары.Номенклатура = ВТ.Номенклатура
|ГДЕ
| РеализацияТовары.Ссылка = &Ссылка
| И ВТ.Номенклатура ЕСТЬ NULL";
Запрос.УстановитьПараметр("ТЗ_Товары", Объект.Товары.Выгрузить());
Запрос.УстановитьПараметр("Ссылка", Объект.Ссылка);
Результат = Запрос.Выполнить().Выгрузить();
Если Результат.Количество() = 0 Тогда
Возврат "";
КонецЕсли;
ТекстРазличий = "";
Для Каждого Стр Из Результат Цикл
ТекстРазличий = ТекстРазличий + Стр.Статус + ": " + Стр.Номенклатура + " (" + Стр.Количество + "); ";
КонецЦикла;
Возврат ТекстРазличий;
КонецФункции
Проанализируем ситуацию: в современных управляемых формах использование модальных окон (которые останавливают выполнение кода) запрещено. Поэтому мы должны использовать асинхронный подход. В обработчике ПередЗаписью на клиенте мы проверим, есть ли изменения, и если они есть — прервем запись, чтобы спросить пользователя о причине.
&НаКлиенте
Процедура ПередЗаписью(Отказ, ПараметрыЗаписи)
// Если причина уже заполнена в текущем сеансе записи, не спрашиваем повторно
Если ПараметрыЗаписи.Свойство("ПричинаВыбрана") Тогда
Возврат;
КонецЕсли;
ТекстИзменений = ПолучитьТекстРазличий();
Если ЗначениеЗаполнено(ТекстИзменений) Тогда
Отказ = Истина; // Прерываем стандартную запись
// Подготавливаем структуру для передачи в форму выбора
ПараметрыФормы = Новый Структура("ВыборПричины", Истина);
ОписаниеОповещения = Новый ОписаниеОповещения("ПослеВыбораПричины", ЭтотОбъект, ПараметрыЗаписи);
// Открываем форму выбора причины
ОткрытьФорму("Справочник.ПричиныИзменений.ФормаВыбора", ПараметрыФормы, ЭтотОбъект, , , , ОписаниеОповещения);
КонецЕсли;
КонецПроцедуры
&НаКлиенте
Процедура ПослеВыбораПричины(РезультатВыбора, ДополнительныеПараметры) Экспорт
Если РезультатВыбора <> Неопределено Тогда
// Запоминаем выбранную причину в объекте через дополнительные свойства или реквизит
Объект.ДополнительныеСвойства.Вставить("ВыбраннаяПричина", РезультатВыбора);
// Добавляем флаг в параметры записи, чтобы не войти в рекурсию
ДополнительныеПараметры.Вставить("ПричинаВыбрана", Истина);
// Повторно вызываем запись
ЭтотОбъект.Записать(ДополнительныеПараметры);
Иначе
ПоказатьПредупреждение(, "Без указания причины изменения не могут быть сохранены!");
КонецЕсли;
КонецПроцедуры
Выясним причину, почему финальную запись в регистр лучше делать в процедуре ПриЗаписи на сервере. Это гарантирует транзакционность: если сам документ не сможет записаться (например, не хватит остатков на складе), то и запись о причине изменения не появится в регистре, что исключит появление "мусорных" данных.
Разберем, как передать причину с клиента на сервер. Мы использовали ДополнительныеСвойства. Посмотрим на серверный код:
&НаСервере
Процедура ПриЗаписи(Отказ)
Если Объект.ДополнительныеСвойства.Свойство("ВыбраннаяПричина") Тогда
Причина = Объект.ДополнительныеСвойства.ВыбраннаяПричина;
ТекстЛога = ПолучитьТекстРазличий(); // Снова получаем текст изменений для записи
МенеджерЗаписи = РегистрыСведений.ИсторияИзмененийДокументов.СоздатьМенеджерЗаписи();
МенеджерЗаписи.Период = ТекущаяДата();
МенеджерЗаписи.Документ = Объект.Ссылка;
МенеджерЗаписи.Пользователь = Пользователи.ТекущийПользователь();
МенеджерЗаписи.Причина = Причина;
МенеджерЗаписи.ОписаниеИзменений = ТекстЛога;
Попытка
МенеджерЗаписи.Записать();
Исключение
ОбщегоНазначенияКлиентСервер.СообщитьПользователю("Ошибка при записи лога изменений: " + КраткоеОписаниеОшибки(ИнформацияОбОшибке()));
КонецПопытки;
КонецЕсли;
КонецПроцедуры
Рассмотрим несколько нюансов, которые помогут сделать систему более надежной:
ПередЗаписью может замедлить работу. В этом случае проанализируйте возможность использования стандартного механизма "Версионирование объектов" из состава БСП (Библиотека стандартных подсистем). Он уже умеет сравнивать версии, но требует доработки для принудительного ввода комментария.Объект.Ссылка еще пустая), запрос к базе ничего не вернет. Обязательно добавьте проверку ЭтоНовый(). Обычно фиксация причин требуется только при редактировании уже существующих (проведенных) документов.Комментарий обязательным для заполнения, если программный код обнаружил изменения в табличной части.Таким образом, мы создали надежный механизм фиксации изменений, который учитывает требования асинхронности современной платформы 1С и гарантирует целостность данных в базе.