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