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