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