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