При разработке в среде «1С:Предприятие» начинающие программисты часто сталкиваются с ситуацией, когда при проведении документа данные из табличной части переносятся в регистр сведений не полностью. Например, в табличной части документа содержится несколько строк, но после проведения в регистре фиксируется лишь одна запись (как правило, последняя). При попытке исправить код в цикле нередко возникает другая ошибка: «Запись с такими ключевыми полями существует!».
В рамках этой статьи мы подробно разберем, почему возникает такое поведение платформы, как устроен механизм уникальности записей в регистрах сведений, и рассмотрим правильные архитектурные решения этой задачи.
Проанализируем исходный код, с которым часто возникает первая проблема (запись только одной строки):
Процедура ОбработкаПроведения(Отказ, Режим)
// Устанавливаем признак необходимости записи движений
Движения.Поставка.Записывать = Истина;
// Создаем ОДНУ запись в наборе движений ДО начала цикла
Движение = Движения.Поставка.Добавить;
Движение.Период = Дата;
Движение.ПоставкаНомерЗакупки = НомерЗакупки;
Движение.ПоставкаНомерПоставки = НомерПоставки;
Движение.ПоставкаДата = Дата;
Для каждого Строка из Поставка Цикл
// В цикле просто перезаписываем свойства одной и той же созданной строки!
Движение.ПоставкаНаименованиеПозиции = Строка.НаименованиеПозиции;
Движение.ПоставкаКоличество = Строка.Количество;
Движение.ПоставкаЦенаЗаЕд = Строка.ЦенаЗаЕд;
Движение.ПоставкаСумма = Строка.Сумма;
КонецЦикла;
КонецПроцедуры
Выясним причину такого поведения. Метод Добавить() вызывается один раз до начала цикла. Платформа выделяет в оперативной памяти одну строку для будущего движения. Затем запускается цикл обхода табличной части документа. На каждой итерации цикла поля этой единственной строки просто перезаписываются новыми значениями. Когда цикл завершается, в памяти остается строка с данными из самой последней строчки табличной части. Именно она и записывается в базу данных.
Для исправления этой логической ошибки необходимо создавать новую запись регистра на каждом шаге цикла. Рассмотрим корректную структуру кода:
Процедура ОбработкаПроведения(Отказ, Режим)
Движения.Поставка.Записывать = Истина;
Для каждого Строка из Поставка Цикл
// Создаем новую запись для каждой строки табличной части внутри цикла
Движение = Движения.Поставка.Добавить;
// Заполняем реквизиты шапки документа
Движение.Период = Дата;
Движение.ПоставкаНомерЗакупки = НомерЗакупки;
Движение.ПоставкаНомерПоставки = НомерПоставки;
Движение.ПоставкаДата = Дата;
// Заполняем данные текущей строки табличной части
Движение.ПоставкаНаименованиеПозиции = Строка.НаименованиеПозиции;
Движение.ПоставкаКоличество = Строка.Количество;
Движение.ПоставкаЦенаЗаЕд = Строка.ЦенаЗаЕд;
Движение.ПоставкаСумма = Строка.Сумма;
КонецЦикла;
КонецПроцедуры
После переноса метода Добавить() внутрь цикла разработчики часто сталкиваются со следующей проблемой — ошибкой уникальности при записи набора. Платформа сообщает, что запись с такими ключевыми полями уже существует.
Разберем, как СУБД контролирует уникальность в регистрах сведений. Регистр сведений — это таблица, уникальность строк в которой жестко определяется ключевыми полями. Состав ключа уникальности зависит от настроек регистра:
Период.Регистратор.Формула ключа уникальности для подчиненного периодического регистра выглядит так:
Ключ = Регистратор + Период + Измерение_1 + Измерение_2 + ... + Измерение_N
Если в вашем регистре сведений нет ни одного измерения (или они не заполняются программно), то для одного документа (Регистратора) в одну и ту же секунду (Период) может существовать только одна запись. При попытке записать вторую строку в цикле СУБД блокирует операцию, так как нарушается требование уникальности первичного ключа.
Рассмотрим несколько вариантов решения проблемы уникальности и проанализируем их жизнеспособность.
Вариант 1. Использование числовых показателей (Количество, Сумма) в качестве измерений (Крайне не рекомендуется)
Иногда разработчики пытаются сделать измерениями изменяющиеся реквизиты, например Количество. Это технически решит проблему до первого документа, в котором встретятся две разные строки с одинаковым количеством товара (например, две разные позиции по 10 штук). Система снова выдаст ошибку уникальности.
Вариант 2. Использование номера строки табличной части в качестве измерения (Технический "костыль")
Добавление в регистр измерения НомерСтроки позволяет добиться уникальности, так как в документе номера строк всегда уникальны. Однако это архитектурно хрупкое решение. Если пользователь интерактивно удалит строку в середине таблицы, поменяет строки местами или отсортирует их, при повторном проведении старые связи в базе данных могут перезаписаться некорректно.
Вариант 3. Использование Ключа связи (Профессиональный стандарт)
В типовых конфигурациях «1С» для связи строк табличной части и движений регистров используется измерение с типом УникальныйИдентификатор (обычно называется КлючСвязи или КлючСтроки). При добавлении строки в табличную часть генерируется уникальный GUID, который записывается и в документ, и в регистр. Это гарантирует абсолютную уникальность и стабильность связей при любых манипуляциях со строками.
Проанализируем характер данных, которые автор пытается записать в регистр сведений: «Количество», «Цена за единицу», «Сумма». Хранение количественно-суммовых показателей в регистре сведений — это грубая архитектурная ошибка.
Регистры сведений предназначены для хранения сведений, характеристик или состояний объектов на определенный момент времени (например, цены номенклатуры, курсы валют, паспортные данные физических лиц). Они хранят последнее актуальное состояние.
Для учета движения ресурсов (сколько поступило товара, на какую сумму, какой остаток образовался) в платформе «1С» реализован другой объект — Регистр накопления. В регистре накопления:
Регистратор + НомерСтрокиЗаписи. Вам не нужно искусственно придумывать уникальные измерения.Если ваша задача — фиксировать факт поставок товаров (приход, расход, обороты), настоятельно рекомендуется переделать этот объект метаданных в Регистр накопления с видом «Обороты» или «Остатки».
Регистры не должны быть точной копией табличной части документа "один в один". В регистр должна попадать очищенная и агрегированная информация. Если пользователь ввел в документ две строки с одинаковым товаром, перед записью в регистр эти данные необходимо свернуть. Это исключит дублирование ключевых аналитик естественным путем.
Рассмотрим пример правильного проведения с предварительной сверткой данных:
Процедура ОбработкаПроведения(Отказ, Режим)
// 1. Обязательно взводим флаг записи движений
Движения.Поставка.Записывать = Истина;
// 2. Очищаем старые движения этого документа в транзакции
Движения.Поставка.Очистить();
// 3. Выгружаем табличную часть во временную Таблицу Значений
ТаблицаДвижений = Поставка.Выгрузить();
// 4. Сворачиваем строки по измерениям регистра (аналитикам)
// В данном случае сворачиваем по Наименованию, суммируя Количество и Сумму
ТаблицаДвижений.Свернуть("НаименованиеПозиции", "Количество, Сумма");
// 5. Записываем уже агрегированные данные в регистр
Для каждого Строка из ТаблицаДвижений Цикл
Движение = Движения.Поставка.Добавить;
Движение.Период = Дата;
Движение.ПоставкаНомерЗакупки = НомерЗакупки;
Движение.ПоставкаНомерПоставки = НомерПоставки;
Движение.ПоставкаДата = Дата;
Движение.ПоставкаНаименованиеПозиции = Строка.НаименованиеПозиции;
Движение.ПоставкаКоличество = Строка.Количество;
// Пересчитываем цену для свернутой строки
Если Строка.Количество <> 0 Тогда
Движение.ПоставкаЦенаЗаЕд = Строка.Сумма / Строка.Количество;
Иначе
Движение.ПоставкаЦенаЗаЕд = 0;
КонецЕсли;
Движение.ПоставкаСумма = Строка.Сумма;
КонецЦикла;
КонецПроцедуры
При записи движений из табличных частей документов придерживайтесь следующих правил:
Добавить() строго внутри цикла обхода строк табличной части.Очистить(), чтобы избежать конфликтов блокировок при перепроведении документа.