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