Как реализовать запись изменений в регистр сведений из вызываемой формы при редактировании документа?

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

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

Анализ проблемы и архитектурный подход

Основная сложность заключается в том, что стандартное событие ПередЗаписью модуля объекта выполняется на сервере. Попытка вызвать из него форму для ввода комментария (как это показано в исходном коде автора темы) приведет к ошибке, так как сервер не может взаимодействовать с интерфейсом пользователя. Нам потребуется разделить логику на три этапа:

  1. Подготовка: сохранение исходного состояния таблицы при открытии формы документа.
  2. Интерактив: сравнение данных на клиенте перед сохранением и вызов диалогового окна для ввода причины изменений.
  3. Запись: передача данных на сервер и фиксация лога в регистре сведений.

Шаг 1. Сохранение исходного состояния данных

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

В начале модуля формы определим экспортную переменную, которая будет хранить наши данные:


Перем ТаблицаДоИзменения Экспорт;

&НаКлиенте
Процедура ПриОткрытии(Отказ)
    // Сохраняем состояние таблицы для последующего сравнения
    ТаблицаДоИзменения = ПолучитьТаблицуИзмененийНаСервере();
КонецПроцедуры

&НаСервере
Функция ПолучитьТаблицуИзмененийНаСервере()
    // Выгружаем товары в таблицу значений для удобства сравнения
    Возврат Объект.Товары.Выгрузить();
КонецФункции

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

Шаг 2. Определение изменений перед записью

Теперь рассмотрим процедуру ПередЗаписью на клиенте. Именно здесь мы проверим, изменилось ли количество, и если да — откроем форму ввода комментария. Для сравнения таблиц нам понадобится вспомогательный алгоритм.

Проанализируем ситуацию: пользователь мог изменить количество в существующей строке, добавить новую или удалить старую. Рассмотрим пример кода для поиска измененных строк:


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

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

Шаг 3. Алгоритм сравнения таблиц

Рассмотрим подробнее функцию ПроверитьНаличиеИзменений. Для точного сопоставления строк лучше всего использовать КлючСтроки (идентификатор), если он предусмотрен в вашей конфигурации. Если нет — сопоставление идет по Номенклатура, но это может быть некорректно при наличии дублей строк с одинаковым товаром.


&НаСервере
Функция ПроверитьНаличиеИзменений()
    ТаблицаИзменений = Новый ТаблицаЗначений;
    ТаблицаИзменений.Колонки.Добавить("Номенклатура");
    ТаблицаИзменений.Колонки.Добавить("Было");
    ТаблицаИзменений.Колонки.Добавить("Стало");

    Для Каждого СтрокаТЧ Из Объект.Товары Цикл
        // Ищем строку в исходной таблице
        ПараметрыПоиска = Новый Структура("Номенклатура", СтрокаТЧ.Номенклатура);
        НайденныеСтроки = ТаблицаДоИзменения.НайтиСтроки(ПараметрыПоиска);
        
        Если НайденныеСтроки.Количество() > 0 Тогда
            // Проверяем изменение количества
            Если НайденныеСтроки[0].Количество <> СтрокаТЧ.Количество Тогда
                НоваяЗапись = ТаблицаИзменений.Добавить();
                НоваяЗапись.Номенклатура = СтрокаТЧ.Номенклатура;
                НоваяЗапись.Было = НайденныеСтроки[0].Количество;
                НоваяЗапись.Стало = СтрокаТЧ.Количество;
            КонецЕсли;
        Иначе
            // Строка добавлена (в исходной ее не было)
            НоваяЗапись = ТаблицаИзменений.Добавить();
            НоваяЗапись.Номенклатура = СтрокаТЧ.Номенклатура;
            НоваяЗапись.Было = 0;
            НоваяЗапись.Стало = СтрокаТЧ.Количество;
        КонецЕсли;
    КонецЦикла;
    
    Возврат ТаблицаИзменений;
КонецФункции

Шаг 4. Обработка закрытия формы и запись данных

После того как пользователь ввел комментарий в вызываемой форме и нажал «ОК», управление возвращается в процедуру оповещения. Выясним причину использования ДополнительныеСвойства: это единственный надежный способ передать произвольные данные из формы в модуль объекта для записи в одной транзакции.


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

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

Шаг 5. Модуль объекта: Запись в Регистр Сведений

Финальный этап происходит в модуле объекта документа. Мы проанализируем содержимое ДополнительныеСвойства и выполним движения по регистру. Использование модуля объекта гарантирует, что если запись документа сорвется, запись в регистр также будет отменена (в рамках одной транзакции).


Процедура ПередЗаписью(Отказ, РежимЗаписи, РежимПроведения)
    
    Если ДополнительныеСвойства.Свойство("ЛогИзменений") Тогда
        ТаблицаЛога = ДополнительныеСвойства.ЛогИзменений;
        Комментарий = ДополнительныеСвойства.КомментарийЛога;
        
        Для Каждого СтрокаЛога Из ТаблицаЛога Цикл
            МенеджерЗаписи = РегистрыСведений.ЛогРедактированияДокументов.СоздатьМенеджерЗаписи();
            МенеджерЗаписи.Период = ТекущаяДата();
            МенеджерЗаписи.Документ = Ссылка;
            МенеджерЗаписи.Товар = СтрокаЛога.Номенклатура;
            МенеджерЗаписи.Было = СтрокаЛога.Было;
            МенеджерЗаписи.Стало = СтрокаЛога.Стало;
            МенеджерЗаписи.Комментарий = Комментарий;
            МенеджерЗаписи.Пользователь = ПараметрыСеанса.ТекущийПользователь;
            
            МенеджерЗаписи.Записать();
        КонецЦикла;
    КонецЕсли;
    
КонецПроцедуры

Альтернативный метод: Сравнение со ссылкой

Ранее мы рассматривали копирование таблицы при открытии. Однако существует способ получить «старые» данные без использования переменных формы. В момент выполнения процедуры ПередЗаписью в базе данных еще хранятся старые значения. Мы можем получить их через обращение к Ссылка.Товары.

Преимущества этого метода:

Недостатки:

Рекомендации по оптимизации

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

  1. Версионирование БСП: если ваша конфигурация типовая (УТ 11, ERP, КА), проверьте, не включена ли стандартная подсистема «Версионирование объектов». Возможно, её функционала будет достаточно без написания кода.
  2. Уникальность строк: используйте скрытый реквизит ТЧ КлючСвязи или ИдентификаторСтроки. Это позволит точно определить, что произошло со строкой, даже если пользователь поменял Номенклатуру на другую.
  3. Регистратор: если ваш регистр сведений подчинен регистратору, запись лога должна идти не через менеджер записи, а через набор записей с установкой отбора по регистратору.

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

← На главную