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