Как изменить существующую запись в регистре сведений при проведении документа 1С, а не создавать новую?

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

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

Особенности работы с регистрами сведений при проведении документов

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

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

Выбор правильного типа регистра сведений: периодический и подчиненный регистратору

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

  1. Подчиненный регистратору: Этот режим записи означает, что записи регистра жестко связаны с документом, который их создал. При отмене проведения документа или его удалении, соответствующие записи в регистре автоматически удаляются. Это обеспечивает целостность данных и упрощает отмену ошибочных действий.
  2. Периодический: Этот режим позволяет хранить данные в разрезе времени. Платформа 1С автоматически добавляет системное измерение "Период" типа "Дата". Это критически важно для отслеживания изменений во времени.
    • Гранулярность периодичности: Вы можете установить периодичность в пределах секунды, дня, месяца, квартала или года. Это определяет точность хранения исторических данных. Для кадрового учета часто используют "День" или даже "Секунда", чтобы можно было учесть несколько событий в один день. Важно понимать, что платформа автоматически приводит значение поля "Период" к началу указанного периода (например, если периодичность "День", то 01.01.2023 15:30:00 станет 01.01.2023 00:00:00). Если регистр подчинен регистратору, то часто используют периодичность "По позиции регистратора", что позволяет отслеживать изменения с точностью до момента проведения документа, сохраняя порядок движений внутри одной даты.
    • Хранение истории: Благодаря периодичности, вы всегда сможете получить данные на любую дату ("срез последних"), а также всю историю изменений.

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

Программный доступ к записям регистра сведений: НаборЗаписей

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

Мы разберем шаги, которые необходимо выполнить в процедуре ОбработкаПроведения вашего документа.

  1. Создание объекта НаборЗаписей:

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

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

    Или, если регистр подчинен регистратору, что является нашим случаем, мы обычно инициализируем его, привязывая к текущему документу:

    
    НаборЗаписей = РегистрыСведений.СостоянияСотрудников.СоздатьНаборЗаписей();
    НаборЗаписей.Отбор.Регистратор.Установить(Ссылка);
    

    Установка отбора по регистратору — обязательное условие для корректной работы с подчиненными регистрами.

  2. Чтение существующих записей:

    Чтобы изменить записи, а не просто добавить новые, мы должны сначала прочитать те записи, которые уже есть в базе данных по нашему отбору. В случае подчиненного регистра, это будут записи, созданные именно этим документом в его предыдущих проведениях (если документ перепроводится).

    
    НаборЗаписей.Прочитать();
    
  3. Установка дополнительных отборов и поиск нужной записи:

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

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

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

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

  4. Изменение или добавление записи:

    Теперь, когда у нас есть набор записей, мы можем с ним работать.

    
    // Инициализируем наш набор для движений текущего документа
    Движения = РегистрыСведений.СостоянияСотрудников;
    Движения.Очистить(); // Очищаем старые движения текущего документа перед формированием новых
    
    НоваяЗапись = Движения.Добавить();
    НоваяЗапись.Период = ДатаУвольнения; // Важно: заполняем Период!
    НоваяЗапись.Сотрудник = СотрудникИзДокумента;
    НоваяЗапись.Состояние = ПредопределенноеЗначение("Перечисление.СостоянияСотрудников.Уволен");
    НоваяЗапись.Должность = Неопределено; // Или актуальная должность на момент увольнения
    НоваяЗапись.ПричинаУвольнения = ПричинаУвольненияИзДокумента; 
    // Заполняем остальные измерения и ресурсы
    

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

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

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

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

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

  5. Запись набора записей:

    После всех изменений и добавлений, мы должны записать наш набор обратно в базу данных.

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

    В версиях 1С 8.3.26 и выше для метода Записать() объекта НаборЗаписей появились дополнительные режимы замещения:

    • РежимЗамещения.Добавление: Добавляет записи в набор.
    • РежимЗамещения.Замещение: Замещает все записи, совпадающие по ключу (измерения + период).
    • РежимЗамещения.Слияние: Обновляет существующие записи, добавляет новые, не трогает записи, не указанные в наборе.
    • РежимЗамещения.Обновление: Обновляет только те записи, которые совпадают по ключу.
    • РежимЗамещения.Удаление: Удаляет записи, совпадающие по ключу.

    Эти режимы дают больше гибкости, но в контексте подчиненных регистров и стандартной логики "Очистить и Добавить" обычно достаточно простого Записать() после Очистить() и Добавить().

Важность поля "Период" и устранение ошибок

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

Например:


НоваяЗапись.Период = ДатаУвольненияДокумента; // Корректно
// НоваяЗапись.Период = Неопределено; // Выдаст ошибку!

Убедитесь, что дата соответствует требуемой гранулярности регистра. Если периодичность "День", то время в дате будет проигнорировано (или приведено к началу дня). Если "Секунда" или "По позиции регистратора", то время будет иметь значение.

Программный доступ к записи как к объекту: МенеджерЗаписи

Автор обсуждения также спрашивал о том, как "программно обратиться к записи как к объекту". Кроме работы с НаборЗаписей, для операций с единичными записями регистра (особенно для независимых регистров или для точечных изменений) используется МенеджерЗаписи.

  1. Создание МенеджерЗаписи:
    
    МенеджерЗаписи = РегистрыСведений.СостоянияСотрудников.СоздатьМенеджерЗаписи();
    
  2. Установка ключевых полей:

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

    
    МенеджерЗаписи.Сотрудник = СотрудникИзДокумента;
    МенеджерЗаписи.Период = ДатаУвольненияДокумента;
    // Установить другие измерения, если они есть и входят в ключ
    
  3. Чтение записи:

    Метод Прочитать() попытается найти запись по установленным ключевым полям. Если запись найдена, её ресурсы будут заполнены в объекте МенеджерЗаписи.

    
    Если МенеджерЗаписи.Прочитать() Тогда
        // Запись найдена, теперь можно изменить её ресурсы
        МенеджерЗаписи.Состояние = ПредопределенноеЗначение("Перечисление.СостоянияСотрудников.Уволен");
        // ...
        МенеджерЗаписи.Записать(); // Записать измененную запись
    Иначе
        // Запись не найдена, можно создать новую, если это необходимо
        МенеджерЗаписи.Состояние = ПредопределенноеЗначение("Перечисление.СостоянияСотрудников.Уволен");
        // Заполнить все измерения и ресурсы
        МенеджерЗаписи.Записать();
    КонецЕсли;
    

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

Регистр сведений или справочник для учета сотрудников?

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

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

Для учета движения кадров (прием, перемещение, увольнение), изменения должностей, подразделений, графиков работы, условий оплаты труда сотрудников, регистр сведений подходит гораздо лучше. Он позволяет хранить полную историю этих изменений с привязкой к датам. Например, сотрудник Петров И.И. с 01.01.2023 был "Менеджером", а с 01.07.2023 стал "Ведущим менеджером". Справочник не может эффективно хранить такую историю без дополнительных механизмов, тогда как периодический регистр сведений делает это нативно.

Заключение и лучшие практики

Таким образом, для задачи изменения данных в регистре сведений при проведении документа, которая была изначально подтянута из другого документа (например, "Увольнение" из "Прием на работу"), наиболее подходящим и гибким решением является использование подчиненного периодического регистра сведений.

Мы подытожим основные моменты:

  1. Выбор регистра: Используйте подчиненный, периодический регистр сведений для хранения истории изменений, связанных с документами.
  2. ОбработкаПроведения: В обработчике проведения документа:
    • Используйте РегистрыСведений.<ИмяРегистра>.СоздатьНаборЗаписей().
    • Установите отбор по регистратору: НаборЗаписей.Отбор.Регистратор.Установить(Ссылка).
    • Прочитайте существующие движения документа: НаборЗаписей.Прочитать().
    • Очистите набор: НаборЗаписей.Очистить(). Это удалит все старые движения, сформированные текущим документом при его предыдущих проведениях.
    • Добавьте новые записи: НоваяЗапись = НаборЗаписей.Добавить().
    • Обязательно заполните поле Период для каждой новой записи, а также все ключевые измерения и ресурсы.
    • Запишите набор: НаборЗаписей.Записать().
  3. Принцип периодичности: Для хранения истории изменений мы не модифицируем старые записи, созданные *другими* документами, напрямую. Вместо этого, мы создаем новые записи с актуальной датой, которые перекрывают предыдущие. Это позволяет получать "срез последних" данных и сохранять полную историю.
  4. Проверка отборов: Всегда внимательно проверяйте условия отбора при работе с регистрами, чтобы гарантировать, что вы воздействуете именно на те записи, которые вам нужны, и избегаете непреднамеренных изменений.

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

← На главную