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

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

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

Анализ исходной проблемы и выбор стратегии

Разберем типичную ситуацию: пользователь меняет количество в таблице Товары. Программа должна запросить комментарий или причину изменения и записать данные (кто, когда, какой товар, старое количество, новое количество и комментарий) в регистр сведений. Основная сложность здесь заключается в том, чтобы данные в регистр попадали только в том случае, если сам документ был успешно записан. Если мы будем писать в регистр сразу при закрытии вспомогательной формы ввода комментария, а пользователь потом закроет основной документ без сохранения, в базе останутся «битые» логи.

Проанализируем три подхода, предложенных в обсуждении:

  1. Использование обработчика ожидания: Метод «костыльный» и ненадежный, так как он срабатывает через интервал времени и не привязан к транзакции записи документа.
  2. Запись напрямую из формы: Простой, но не всегда корректный метод, если логика записи сложная или требует проверки прав доступа.
  3. Использование ДополнительныеСвойства: Наиболее профессиональный метод, позволяющий передать данные из контекста формы в контекст объекта (модуль) и выполнить запись в одной транзакции с документом.

Шаг 1: Получение данных из текущей строки табличной части

Прежде чем что-то записывать, нам нужно понять, какая именно строка редактируется. Для получения данных текущей строки в форме документа используем свойство ТекущиеДанные. Рассмотрим пример кода для обработчика ПриИзменении колонки количества:


Процедура ТоварыКоличествоПриИзменении(Элемент)
    СтрТовары = Элементы.Товары.ТекущиеДанные; // Для управляемых форм
    // В обычных формах: СтрТовары = ЭлементыФормы.Товары.ТекущиеДанные;
    
    Если СтрТовары <> Неопределено Тогда
        Товар = СтрТовары.Номенклатура;
        Количество = СтрТовары.Количество;
        Цена = СтрТовары.Цена;
        
        // Здесь можно вызвать форму для ввода комментария
        // ...
    КонецЕсли;
КонецПроцедуры

Шаг 2: Проблема идентификации строк

Выясним важный нюанс: как программа поймет, какая именно строка была изменена, если в таблице несколько одинаковых товаров? Если в регистр записывать просто по ссылке на номенклатуру, мы запутаемся при наличии дублей. Рекомендуется добавить в табличную часть скрытый реквизит ИдентификаторСтроки с типом Строка или УникальныйИдентификатор.

Проанализируем, как заполнять этот идентификатор. Это нужно делать в событии ПриДобавлении или ПриКопировании строки ТЧ. Это позволит нам четко связать запись в логе с конкретной строкой в документе.

Шаг 3: Передача данных через ДополнительныеСвойства

Рассмотрим основной механизм. ДополнительныеСвойства — это структура, которая есть у любого объекта (документа, справочника). Она существует только в памяти во время сеанса записи. Это идеальное место для передачи «сопроводительной» информации из формы в модуль.

В форме документа, перед тем как вызвать стандартную команду записи, мы заполняем эту структуру:


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

Шаг 4: Запись в регистр в модуле объекта

Теперь переместимся в модуль объекта (или создадим Подписку на событие ПриЗаписи). Здесь мы извлекаем нашу таблицу и записываем её в регистр сведений. Посмотрим, как это выглядит в коде:


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

Шаг 5: Реализация упрощенного варианта (через модуль формы)

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


Процедура ПередЗаписью(Отказ, РежимЗаписи, РежимПроведения)
    // Предположим, ИзмененияКоличества - это переменная модуля формы (ТаблицаЗначений)
    НаборЗаписей = РегистрыСведений.ЛогРедактированияДокументов.СоздатьНаборЗаписей();
    
    Для Каждого Стр Из ИзмененияКоличества Цикл
        // Проверяем, изменилось ли значение на самом деле по сравнению с первоначальным
        // (ПервоначальныеДанные мы должны были сохранить при открытии формы)
        МассивСтрок = ПервоначальныеДанные.НайтиСтроки(Новый Структура("ИдентификаторСтроки", Стр.ИдентификаторСтроки));
        Если МассивСтрок.Количество() > 0 И МассивСтрок[0].Количество = Стр.КоличествоСтало Тогда
            Продолжить; // Изменений нет, пропускаем
        КонецЕсли;
        
        НоваяЗапись = НаборЗаписей.Добавить();
        ЗаполнитьЗначенияСвойств(НоваяЗапись, Стр);
        НоваяЗапись.Документ = Ссылка;
        НоваяЗапись.Пользователь = ПараметрыСеанса.ТекущийПользователь;
        НоваяЗапись.Период = ТекущаяДата();
    КонецЦикла;
    
    // Записываем набор. Если это новый документ, Ссылка заполнится после записи самого документа
    // Поэтому в идеале этот код лучше перенести в ПриЗаписи
    НаборЗаписей.Записать(Ложь);
КонецПроцедуры

Важные рекомендации по работе с регистрами логов

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

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

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

← На главную