Почему при проведении документа в регистр сведений записывается только одна строка и как это исправить?

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

При разработке в среде «1С:Предприятие» начинающие программисты часто сталкиваются с ситуацией, когда при проведении документа данные из табличной части переносятся в регистр сведений не полностью. Например, в табличной части документа содержится несколько строк, но после проведения в регистре фиксируется лишь одна запись (как правило, последняя). При попытке исправить код в цикле нередко возникает другая ошибка: «Запись с такими ключевыми полями существует!».

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

Причина №1: Неверное расположение метода добавления записи в цикле

Проанализируем исходный код, с которым часто возникает первая проблема (запись только одной строки):


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

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

Для исправления этой логической ошибки необходимо создавать новую запись регистра на каждом шаге цикла. Рассмотрим корректную структуру кода:


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

Причина №2: Ошибка уникальности записей (Ключ записи регистра)

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

Разберем, как СУБД контролирует уникальность в регистрах сведений. Регистр сведений — это таблица, уникальность строк в которой жестко определяется ключевыми полями. Состав ключа уникальности зависит от настроек регистра:

  1. Периодичность: Если регистр периодический, то в ключ уникальности входит поле Период.
  2. Режим записи: Если регистр подчинен регистратору, в ключ автоматически входит поле Регистратор.
  3. Измерения: Все объекты метаданных, добавленные в ветку «Измерения» регистра сведений, составляют основу его уникальности.

Формула ключа уникальности для подчиненного периодического регистра выглядит так:

Ключ = Регистратор + Период + Измерение_1 + Измерение_2 + ... + Измерение_N

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

Как обеспечить уникальность: правильные и неправильные подходы

Рассмотрим несколько вариантов решения проблемы уникальности и проанализируем их жизнеспособность.

Вариант 1. Использование числовых показателей (Количество, Сумма) в качестве измерений (Крайне не рекомендуется)

Иногда разработчики пытаются сделать измерениями изменяющиеся реквизиты, например Количество. Это технически решит проблему до первого документа, в котором встретятся две разные строки с одинаковым количеством товара (например, две разные позиции по 10 штук). Система снова выдаст ошибку уникальности.

Вариант 2. Использование номера строки табличной части в качестве измерения (Технический "костыль")

Добавление в регистр измерения НомерСтроки позволяет добиться уникальности, так как в документе номера строк всегда уникальны. Однако это архитектурно хрупкое решение. Если пользователь интерактивно удалит строку в середине таблицы, поменяет строки местами или отсортирует их, при повторном проведении старые связи в базе данных могут перезаписаться некорректно.

Вариант 3. Использование Ключа связи (Профессиональный стандарт)

В типовых конфигурациях «1С» для связи строк табличной части и движений регистров используется измерение с типом УникальныйИдентификатор (обычно называется КлючСвязи или КлючСтроки). При добавлении строки в табличную часть генерируется уникальный GUID, который записывается и в документ, и в регистр. Это гарантирует абсолютную уникальность и стабильность связей при любых манипуляциях со строками.

Архитектурный выбор: Регистр сведений или Регистр накопления?

Проанализируем характер данных, которые автор пытается записать в регистр сведений: «Количество», «Цена за единицу», «Сумма». Хранение количественно-суммовых показателей в регистре сведений — это грубая архитектурная ошибка.

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

Для учета движения ресурсов (сколько поступило товара, на какую сумму, какой остаток образовался) в платформе «1С» реализован другой объект — Регистр накопления. В регистре накопления:

Если ваша задача — фиксировать факт поставок товаров (приход, расход, обороты), настоятельно рекомендуется переделать этот объект метаданных в Регистр накопления с видом «Обороты» или «Остатки».

Оптимизация: Консолидация (свертка) данных перед записью

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

Рассмотрим пример правильного проведения с предварительной сверткой данных:


Процедура ОбработкаПроведения(Отказ, Режим)
    // 1. Обязательно взводим флаг записи движений
    Движения.Поставка.Записывать = Истина;
    
    // 2. Очищаем старые движения этого документа в транзакции
    Движения.Поставка.Очистить(); 
    
    // 3. Выгружаем табличную часть во временную Таблицу Значений
    ТаблицаДвижений = Поставка.Выгрузить();
    
    // 4. Сворачиваем строки по измерениям регистра (аналитикам)
    // В данном случае сворачиваем по Наименованию, суммируя Количество и Сумму
    ТаблицаДвижений.Свернуть("НаименованиеПозиции", "Количество, Сумма");
    
    // 5. Записываем уже агрегированные данные в регистр
    Для каждого Строка из ТаблицаДвижений Цикл
        Движение = Движения.Поставка.Добавить;
        Движение.Период = Дата;
        Движение.ПоставкаНомерЗакупки = НомерЗакупки;
        Движение.ПоставкаНомерПоставки = НомерПоставки;
        Движение.ПоставкаДата = Дата;
        
        Движение.ПоставкаНаименованиеПозиции = Строка.НаименованиеПозиции;
        Движение.ПоставкаКоличество = Строка.Количество;
        
        // Пересчитываем цену для свернутой строки
        Если Строка.Количество <> 0 Тогда
            Движение.ПоставкаЦенаЗаЕд = Строка.Сумма / Строка.Количество;
        Иначе
            Движение.ПоставкаЦенаЗаЕд = 0;
        КонецЕсли;
        
        Движение.ПоставкаСумма = Строка.Сумма;
    КонецЦикла;
КонецПроцедуры

Резюме и рекомендации

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

← На главную