Как автоматически фиксировать изменения статуса клиента в периодическом регистре сведений 1С и избежать ошибок?

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

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

Анализируем исходную проблему: некорректная запись в регистр

Первоначальная задача состоит в том, чтобы изменения статуса клиента, заданного реквизитом "Статус" справочника "Контрагенты", автоматически фиксировались в регистре сведений "ИзменениеСтатуса". Этот регистр имеет измерение "Контрагент" и ресурс "Статус", а также является периодическим. Для начала мы могли бы попробовать использовать следующий подход в модуле формы справочника "Контрагенты" при изменении реквизита "Статус":


&НаСервере
Процедура СтатусКлиентаПриИзмененииНаСервере()
    РегистрыСведений.ИзменениеСтатуса.ПолучитьПоследнее;
КонецПроцедуры

&НаКлиенте
Процедура СтатусКлиентаПриИзменении(Элемент)
    СтатусКлиентаПриИзмененииНаСервере;
КонецПроцедуры

Однако, как мы выясним, этот код не будет записывать никакие данные. Метод ПолучитьПоследнее предназначен для чтения последней записи регистра по набору отборов, а не для создания или изменения записей. Это важный момент, который указывает на необходимость использования других методов для манипуляции данными регистров сведений, таких как создание нового набора записей, добавление в него элементов и последующая запись.

Выявляем особенности периодических регистров сведений

В процессе работы с регистром сведений "ИзменениеСтатуса" мы сталкиваемся с ошибкой: "Запись не верна! Период не может быть пустым!: ИзменениеСтатуса: 01.01.0001". Эта ошибка возникает, потому что наш регистр сведений является периодическим. Это означает, что для каждой записи в таком регистре необходимо обязательно указывать значение для поля Период. Поле Период является частью ключа записи периодического регистра и определяет момент времени, к которому относится информация.

Если мы не укажем Период, система не сможет однозначно идентифицировать запись и сохранить её. Для корректной работы с периодическими регистрами всегда необходимо присваивать этому полю актуальную дату и время. Чаще всего для этой цели используется функция ТекущаяДата(), которая возвращает текущую дату и время с точностью до секунды.

Выбираем правильное место для размещения кода

Изначально мы могли бы рассмотреть размещение логики записи в событии формы, таком как ПриИзменении реквизита "Статус". Однако такой подход имеет существенные ограничения. Если статус клиента будет изменён программно (например, регламентной обработкой или в процессе обмена данными) или через другие формы, не связанные с нашей формой элемента справочника, то логика в модуле формы просто не сработает.

Поэтому критически важно размещать логику фиксации изменений статуса в модуле объекта справочника "Контрагенты". Оптимальным событием для этого является ПриЗаписиНаСервере. Этот обработчик срабатывает всякий раз, когда объект справочника записывается в базу данных, независимо от того, как было инициировано изменение — через пользовательский интерфейс, программно или в результате обмена.

Сигнатура процедуры ПриЗаписиНаСервере в модуле объекта выглядит так:


&НаСервере
Процедура ПриЗаписиНаСервере(Отказ, ТекущийОбъект, ПараметрыЗаписи)
    // Ваш код здесь
КонецПроцедуры

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

Реализуем логику автоматической записи статуса

Теперь, когда мы определились с местом размещения кода, давайте пошагово рассмотрим, как реализовать корректную логику записи изменений статуса в периодический регистр сведений.

Шаг 1: Создаем набор записей регистра.

Для работы с регистром сведений мы должны создать объект "НаборЗаписей" для нашего регистра "ИзменениеСтатуса". Это позволяет нам манипулировать записями регистра.

НаборЗаписей = РегистрыСведений.ИзменениеСтатуса.СоздатьНаборЗаписей();

Шаг 2: Устанавливаем отбор по контрагенту.

Чтобы получить только те записи, которые относятся к текущему контрагенту, мы устанавливаем отбор по измерению "Контрагент".


НаборЗаписей.Отбор.Контрагент.Установить(Объект.Ссылка); // Используем Объект.Ссылка для ссылки на текущий записываемый элемент

Шаг 3: Считываем существующие записи.

После установки отбора, мы считываем записи из базы данных в наш набор записей. Это важно, чтобы мы могли проверить текущее состояние и избежать дублирующих записей.

НаборЗаписей.Прочитать();

Шаг 4: Проверяем, изменился ли статус.

Чтобы избежать избыточных записей в истории, нам нужно проверить, действительно ли текущий статус контрагента отличается от последнего записанного статуса в регистре. Если статус не изменился, новую запись добавлять не нужно.


ПоследнийСтатус = Неопределено;
Если НаборЗаписей.Количество > 0 Тогда
    // Получаем последнюю запись из набора.
    // Если регистр периодический и отбор только по измерениям, то последняя запись будет самой актуальной.
    // Для более строгой проверки можно запросом получить последнюю по Периоду.
    ЗаписьРегистра = НаборЗаписей[НаборЗаписей.Количество - 1]; 
    ПоследнийСтатус = ЗаписьРегистра.Статус;
КонецЕсли;

Если ПоследнийСтатус <> Объект.Статус Тогда
    // Статус изменился или это первая запись для контрагента
    // Продолжаем к шагу 5
КонецЕсли;

Шаг 5: Добавляем новую запись, если статус изменился или записей нет.

Если статус изменился или для данного контрагента еще нет записей в регистре, мы создаем новую запись в наборе.


ЗаписьРегистра = НаборЗаписей.Добавить();
ЗаписьРегистра.Период    = ТекущаяДата();      // Обязательно устанавливаем период!
ЗаписьРегистра.Контрагент = Объект.Ссылка;
ЗаписьРегистра.Статус   = Объект.Статус;

Шаг 6: Записываем набор.

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

Корректный подход к записи в периодический регистр сведений (для добавления истории):

Вместо того, чтобы читать и перезаписывать весь набор, мы можем просто создать одну новую запись через менеджер записи и записать её, если статус изменился. Это более эффективно и правильно для регистрации исторических данных.


&НаСервере
Процедура ПриЗаписиНаСервере(Отказ, ТекущийОбъект, ПараметрыЗаписи)

    // 1. Получим последний статус контрагента из регистра сведений
    Запрос = Новый Запрос;
    Запрос.Текст = 
        "ВЫБРАТЬ ПЕРВЫЕ 1
        |    ИзменениеСтатусаСрезПоследних.Статус
        |ИЗ
        |    РегистрСведений.ИзменениеСтатуса.СрезПоследних(&Период, Контрагент = &Контрагент) КАК ИзменениеСтатусаСрезПоследних";

    Запрос.УстановитьПараметр("Контрагент", ТекущийОбъект.Ссылка);
    Запрос.УстановитьПараметр("Период", ТекущаяДата()); // Срез последних на текущий момент

    РезультатЗапроса = Запрос.Выполнить();
    Выборка = РезультатЗапроса.Выбрать();

    ПоследнийСтатус = Неопределено;
    Если Выборка.Следующий() Тогда
        ПоследнийСтатус = Выборка.Статус;
    КонецЕсли;

    // 2. Сравниваем текущий статус объекта с последним записанным
    Если ПоследнийСтатус <> ТекущийОбъект.Статус Тогда

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

        Попытка
            МенеджерЗаписи.Записать();
        Исключение
            Сообщить("Ошибка при записи статуса клиента в регистр: " + ОписаниеОшибки());
            Отказ = Истина; // Отменяем запись контрагента, если не удалось записать статус
        КонецПопытки;

    КонецЕсли;

КонецПроцедуры

В этом примере мы используем МенеджерЗаписи для добавления новой записи, что является наиболее адекватным подходом для периодических регистров, когда мы хотим именно добавлять новые "точки" истории. Проверка на изменение статуса с помощью среза последних позволяет избежать ненужных записей, когда контрагент сохраняется, но его статус фактически не менялся.

Анализируем причину ошибки "Период не может быть пустым" и её решение

Как мы выяснили в ходе обсуждения, ошибка "Период не может быть пустым!" проявлялась не для всех контрагентов, а только для некоторых. При более детальном рассмотрении оказалось, что эти "проблемные" контрагенты имели записи в регистре сведений "ИзменениеСтатуса", которые были созданы вручную. При ручном создании записей пользователи могли забыть или не знать о необходимости заполнить поле Период.

Когда система пыталась работать с таким регистром (например, при попытке получить срез последних или записать новую запись), она натыкалась на некорректные записи с пустым Периодом, что приводило к ошибке. Даже если новая запись создавалась бы корректно, наличие некорректных старых записей могло мешать работе запросов или других механизмов.

Решение этой проблемы:

  1. Удаление некорректных записей: Самый простой способ – это найти и удалить все записи в регистре сведений "ИзменениеСтатуса", у которых поле Период не заполнено (равно значению по умолчанию 01.01.0001 0:00:00).
  2. Корректировка существующих записей: Если удаление невозможно (например, данные важны), необходимо вручную или программно заполнить корректными датами поле Период для всех таких записей.

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

Отображение истории статусов и текущего статуса

Помимо автоматической записи, нам необходимо также отображать текущий статус клиента и историю его изменений. Мы можем реализовать это следующими способами:

  1. Текущий статус: Чтобы показать актуальный статус в форме элемента справочника "Контрагенты" или в списке, мы можем создать вычисляемое поле, которое будет получать данные из регистра сведений "ИзменениеСтатуса" с помощью запроса по срезу последних. Этот запрос будет выбирать последнюю запись для текущего контрагента.
  2. История статусов: Для отображения полной истории изменений статуса непосредственно в карточке клиента, мы можем добавить на форму элемента справочника "Контрагенты" элемент управления типа "Динамический список". Источником данных для этого динамического списка будет служить регистр сведений "ИзменениеСтатуса", настроенный с отбором по текущему контрагенту и сортировкой по полю Период. Таким образом, пользователь сможет видеть все изменения статусов клиента в хронологическом порядке.

Реализация автоматической фиксации изменений статуса клиента в периодическом регистре сведений требует внимания к деталям, особенно в части размещения кода и корректной работы с полем Период. Разместив логику в обработчике ПриЗаписиНаСервере модуля объекта справочника и обеспечив правильное заполнение всех ключевых полей, мы создаем надежный механизм для отслеживания истории изменений, который будет работать стабильно независимо от способа изменения данных.

← На главную