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