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