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