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