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