При разработке функционала в 1С часто возникает необходимость установить дополнительные проверки и запретить запись или проведение документа при определенных условиях. Эта задача особенно актуальна при работе с расширениями, когда требуется добавить специфическую логику, не затрагивая типовую конфигурацию. Рассмотрим подробнее, как корректно реализовать такой запрет, избегая распространенных ошибок.
Для запрета записи документа мы будем работать с событием ПередЗаписью, которое вызывается непосредственно перед тем, как объект документа будет сохранен в базу данных. Это идеальный момент для проведения всех необходимых проверок.
В расширении для работы с событиями типовой конфигурации используются специальные директивы. Рассмотрим основные из них:
Отказ = Истина (чтобы отменить запись), директива &Перед является наиболее подходящей. Она позволяет нам прервать выполнение стандартной логики записи до ее начала.&После означает, что наш код будет выполнен после того, как отработает оригинальная процедура. Если мы попытаемся установить Отказ = Истина в обработчике с директивой &После, это часто бывает неэффективно, потому что стандартная процедура записи уже могла завершиться или уже приняла решение о возможности записи. Нам нужно отменить запись до ее совершения, а не после. Именно использование &После вместо &Перед часто приводит к тому, что документ записывается, несмотря на наши условия.Вывод: Для запрета записи документа путем установки параметра Отказ всегда используем директиву &Перед.
Одной из наиболее частых причин ошибки "несоответствие типа возвращаемого параметра" при работе с расширениями является неверное определение параметров в обработчике события ПередЗаписью. Стандартная процедура ПередЗаписью в модуле объекта документа имеет следующую сигнатуру:
Процедура ПередЗаписью(Отказ, РежимЗаписи, РежимПроведения)
// Ваш код
КонецПроцедуры
Давайте разберем каждый параметр:
Отказ (Булево): Это основной параметр, который мы будем изменять. Если мы установим Отказ = Истина, запись документа будет отменена. Этот параметр передается по ссылке, что позволяет нашему обработчику изменять его значение.РежимЗаписи (РежимЗаписиДокумента): Этот параметр указывает, в каком режиме происходит запись документа (например, Запись или Проведение). Он передается по значению, то есть мы можем его читать, но не изменять.РежимПроведения (РежимПроведенияДокумента): Этот параметр уточняет режим проведения (например, Оперативный или Неоперативный). Также передается по значению.Распространенная ошибка: Некоторые разработчики по ошибке пытаются передать в процедуру сам объект документа как первый параметр (например, Процедура РасшЗапрет_ПередЗаписью(Объект, Отказ, РежимЗаписи)). Это происходит из-за путаницы с другими обработчиками, такими как ОбработкаПроведения, где объект документа действительно передается. Однако для ПередЗаписью в модуле объекта документа сам документ доступен через неявную переменную ЭтотОбъект.
Когда система пытается вызвать наш обработчик &Перед("ПередЗаписью") с некорректной сигнатурой, она сталкивается с тем, что типы переданных параметров не совпадают с ожидаемыми. Например, если мы объявим процедуру так: Процедура РасшЗапрет_ПередЗаписью(Объект, Отказ, РежимЗаписи), то при вызове система попытается передать ей оригинальные параметры: Отказ (булево) в Объект, РежимЗаписи (перечисление) в Отказ, и РежимПроведения (перечисление) в РежимЗаписи. Очевидно, что Булево не соответствует типу Объект, а РежимЗаписиДокумента не соответствует типу Булево, что и приводит к ошибке "несоответствие типа возвращаемого параметра" или "несоответствие типов параметров".
Важно: Всегда используйте точную сигнатуру (Отказ, РежимЗаписи, РежимПроведения) для обработчика ПередЗаписью в модуле объекта.
Предположим, у нас есть документ "ЗаказКлиента" и мы хотим запретить его запись при определенных условиях. Код в модуле объекта расширения будет выглядеть так:
// В модуле объекта документа "ЗаказКлиента" расширения
&Перед("ПередЗаписью")
Процедура РасшЗапрет_ПередЗаписью(Отказ, РежимЗаписи, РежимПроведения)
// Вызываем процедуру в общем модуле для основной логики проверки
// Передаем текущий объект документа и параметр Отказ
Расш_ПроверкиДокументов.ПроверитьЗаказПередЗаписью(ЭтотОбъект, Отказ);
// Если Отказ установлен в Истина внутри ПроверитьЗаказПередЗаписью,
// то запись документа будет прервана.
// Сообщение пользователю также должно быть выведено внутри ПроверитьЗаказПередЗаписью.
КонецПроцедуры
Обратите внимание, что мы используем ЭтотОбъект для доступа к текущему документу. Параметр Отказ передается по ссылке в нашу вспомогательную процедуру в общем модуле, что позволяет ей влиять на процесс записи.
Когда мы запрещаем запись, нам необходимо проверить данные документа. Эти данные могут быть как для нового документа (который еще не записан в базу), так и для существующего (в который внесли изменения). Наша логика должна корректно обрабатывать оба сценария.
Внутри процедуры ПередЗаписью (или в процедуре общего модуля, куда мы передаем ЭтотОбъект) текущий документ доступен как объект. Мы можем напрямую обращаться к его реквизитам и табличным частям:
// Пример доступа к реквизитам и табличным частям
Если ЭтотОбъект.Организация.ИНН = "1234567890" Тогда
Для Каждого СтрокаТабЧасти Из ЭтотОбъект.Товары Цикл
Если СтрокаТабЧасти.Номенклатура.ВидНоменклатуры.Наименование = "Продукция Ветис" Тогда
// Условие выполнено
КонецЕсли;
КонецЦикла;
КонецЕсли;
Для определения, является ли документ новым или уже существующим в базе, мы можем использовать свойство ЭтоНовый объекта документа. Свойство ЭтоНовый будет Истина для документа, который еще ни разу не был записан в базу данных и не имеет постоянной ссылки.
Если ЭтотОбъект.ЭтоНовый Тогда
// Документ новый
Иначе
// Документ уже существует в базе
КонецЕсли;
Аналогичную проверку можно сделать с помощью ЗначениеЗаполнено(Ссылка). Если ЗначениеЗаполнено(Ссылка) возвращает Ложь, это означает, что у документа нет сформированной ссылки, то есть он новый.
Если НЕ ЗначениеЗаполнено(ЭтотОбъект.Ссылка) Тогда
// Документ новый
Иначе
// Документ уже существует в базе
КонецЕсли;
Внимание: Если ваше условие запрета записи применимо как к новым, так и к уже существующим документам, то условие НЕ ЗначениеЗаполнено(Ссылка) в проверке запрета может быть излишним, как это было в сообщениях форума. Убедитесь, что логика охватывает все необходимые сценарии.
Иногда для проверки условий требуется получить данные, агрегированные из табличной части документа, или выполнить более сложную логику, которую удобнее реализовать с помощью запроса. Однако, если документ новый или в его табличную часть были внесены изменения, эти изменения еще не находятся в базе данных. Поэтому прямой запрос к таблицам базы данных не покажет актуальную информацию.
Для решения этой проблемы мы можем использовать временные таблицы в запросе, передавая туда данные из табличных частей текущего документа:
// В общем модуле, процедура ПроверитьЗаказПередЗаписью
Процедура ПроверитьЗаказПередЗаписью(ДокументОбъект, Отказ) Экспорт
// Создаем запрос
Запрос = Новый Запрос;
// Определяем временную таблицу "втТовары" и заполняем ее данными из табличной части "Товары" документа
Запрос.Текст =
"ВЫБРАТЬ
| тзТовары.Номенклатура КАК Номенклатура,
| тзТовары.Количество КАК Количество,
| тзТовары.Сумма КАК Сумма
|ПОМЕСТИТЬ втТовары
|ИЗ
| &тзТовары КАК тзТовары
|;
|
////////////////////////////////////////////////////////////////////////////////
|ВЫБРАТЬ
| втТовары.Номенклатура.Наименование КАК НаименованиеНоменклатуры,
| втТовары.Номенклатура.ВидНоменклатуры КАК ВидНоменклатуры,
| СУММА(втТовары.Количество) КАК ОбщееКоличество
|ИЗ
| втТовары КАК втТовары
|ГДЕ
| втТовары.Номенклатура.ВидНоменклатуры.Наименование = &НаименованиеВидаНоменклатурыЗапрещенной
|
|СГРУППИРОВАТЬ ПО
| втТовары.Номенклатура.Наименование,
| втТовары.Номенклатура.ВидНоменклатуры
|
|ИМЕЮЩИЕ
| СУММА(втТовары.Количество) > 10"; // Например, запрет, если общее количество такого товара > 10
// Передаем табличную часть "Товары" документа в параметр запроса
Запрос.УстановитьПараметр("тзТовары", ДокументОбъект.Товары.Выгрузить());
Запрос.УстановитьПараметр("НаименованиеВидаНоменклатурыЗапрещенной", "Продукция Ветис");
РезультатЗапроса = Запрос.Выполнить();
Если НЕ РезультатЗапроса.Пустой() Тогда
Отказ = Истина; // Устанавливаем отказ записи
ВызватьИсключение НСтр("ru = 'Нельзя записать документ, так как общее количество запрещенной продукции превышает лимит!'");
КонецЕсли;
// Дополнительные проверки, не требующие запроса
Если ДокументОбъект.СуммаДокумента < 1000 И ДокументОбъект.Организация.ИНН = "9876543210" Тогда
Отказ = Истина;
ВызватьИсключение НСтр("ru = 'Для данной организации сумма документа не может быть меньше 1000!'");
КонецЕсли;
КонецПроцедуры
В этом примере мы делаем следующее:
втТовары и наполняем ее данными из табличной части Товары текущего документа, используя метод Выгрузить(). Это позволяет запросу работать с актуальными, еще не записанными в базу данными.РезультатЗапроса.Пустой() является Ложь), это означает, что условие запрета выполнено.Для предотвращения записи документа у нас есть два основных механизма:
Отказ = Истина;:
Это основной и рекомендуемый способ. Когда в процедуре ПередЗаписью мы устанавливаем Отказ в Истина, система понимает, что запись должна быть прервана. При этом пользователю обычно выводится информационное сообщение, которое мы можем добавить с помощью Сообщить("Текст сообщения"); или, что чаще, вместе с ВызватьИсключение.
Преимущество: Это мягкий способ отмены, который позволяет системе корректно завершить процесс, не вызывая немедленного прерывания выполнения всего кода, и обеспечивает более контролируемое поведение. Другие обработчики ПередЗаписью (если они есть) также увидят установленный Отказ.
ВызватьИсключение "Текст сообщения";:
Оператор ВызватьИсключение немедленно прерывает выполнение текущей процедуры и выводит сообщение пользователю. Это более "жесткий" способ остановить запись.
Преимущество: Гарантирует немедленное прекращение выполнения и отображение сообщения. В некоторых случаях это может быть предпочтительнее для того, чтобы пользователь сразу увидел критическое сообщение и не продолжал работу, не исправив ошибку.
Рекомендация: Часто эти два механизма используются в связке. Мы устанавливаем Отказ = Истина; и сразу же после этого вызываем ВызватьИсключение "Сообщение";. Это обеспечивает и корректную отмену записи, и немедленное информирование пользователя.
Для лучшей структуризации кода и возможности переиспользования рекомендуется выносить сложную логику проверки в отдельные процедуры общих модулей. Эти процедуры можно вызывать из обработчика события в модуле объекта расширения.
// В новом или существующем общем модуле расширения, например, "Расш_ПроверкиДокументов"
// Убедитесь, что модуль имеет свойство "Сервер" и "Вызов сервера"
&НаСервере
Процедура ПроверитьЗаказПередЗаписью(ДокументОбъект, Отказ) Экспорт
// Проверка 1: Запрет на запись, если количество определенной номенклатуры > 0
// и определенная организация
ЗапретУстановлен = Ложь;
// Пример условия: если в табличной части "Товары" есть номенклатура с видом "Продукция Ветис"
// и документ оформлен для организации с ИНН "1234567890"
Если ДокументОбъект.Организация.ИНН = "1234567890" Тогда
Для Каждого СтрокаТовара Из ДокументОбъект.Товары Цикл
// Проверяем, существует ли ссылка (для избежания ошибки при работе с незаписанной номенклатурой)
Если ЗначениеЗаполнено(СтрокаТовара.Номенклатура) И СтрокаТовара.Номенклатура.ВидНоменклатуры.Наименование = "Продукция Ветис" Тогда
ЗапретУстановлен = Истина;
Прервать; // Выходим из цикла, условие выполнено
КонецЕсли;
КонецЦикла;
КонецЕсли;
Если ЗапретУстановлен Тогда
Отказ = Истина; // Устанавливаем отказ
ВызватьИсключение НСтр("ru = 'Нельзя записать документ: запрещено использование продукции Ветис для данной организации!'");
КонецЕсли;
// Проверка 2: Запрет, если сумма документа меньше 500 для всех новых документов
Если ДокументОбъект.ЭтоНовый И ДокументОбъект.СуммаДокумента < 500 Тогда
Отказ = Истина;
ВызватьИсключение НСтр("ru = 'Сумма нового документа не может быть меньше 500!'");
КонецЕсли;
// Проверка 3: Более сложная проверка с запросом, как обсуждалось ранее
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ
| тзТовары.Номенклатура КАК Номенклатура,
| тзТовары.Количество КАК Количество
|ПОМЕСТИТЬ втНоменклатураСКоличеством
|ИЗ
| &ТЗТовары КАК тзТовары
|;
|
////////////////////////////////////////////////////////////////////////////////
|ВЫБРАТЬ
| втНоменклатураСКоличеством.Номенклатура.ВидНоменклатуры КАК ВидНоменклатуры,
| СУММА(втНоменклатураСКоличеством.Количество) КАК ОбщееКоличество
|ИЗ
| втНоменклатураСКоличеством КАК втНоменклатураСКоличеством
|ГДЕ
| втНоменклатураСКоличеством.Номенклатура.ВидНоменклатуры.Наименование = &ЗапрещенныйВидНоменклатуры
|
|СГРУППИРОВАТЬ ПО
| втНоменклатураСКоличеством.Номенклатура.ВидНоменклатуры
|
|ИМЕЮЩИЕ
| СУММА(втНоменклатураСКоличеством.Количество) > 100"; // Если общее количество такого вида > 100
Запрос.УстановитьПараметр("ТЗТовары", ДокументОбъект.Товары.Выгрузить());
Запрос.УстановитьПараметр("ЗапрещенныйВидНоменклатуры", "Опасные товары");
РезультатЗапроса = Запрос.Выполнить();
Если НЕ РезультатЗапроса.Пустой() Тогда
Отказ = Истина;
ВызватьИсключение НСтр("ru = 'Нельзя записать документ: общее количество опасных товаров превышает допустимый лимит!'");
КонецЕсли;
КонецПроцедуры
В этом примере:
ПроверитьЗаказПередЗаписью объявлена с директивой &НаСервере, так как она будет работать с серверными объектами (запросы, объекты документов).ДокументОбъект (ссылка на текущий документ) и Отказ (параметр, который будет изменен).Отказ = Истина; и выводится сообщение с помощью ВызватьИсключение.&Перед("ПередЗаписью"), который будет вызывать вашу процедуру из общего модуля, как показано выше.Сервер и Вызов сервера в Истина.Если вы сталкиваетесь с неожиданным поведением или ошибками, используйте отладчик 1С. Установите точки останова в вашем обработчике ПередЗаписью и в процедуре общего модуля. Пошагово пройдитесь по коду, проверяя значения параметров (Отказ, РежимЗаписи, РежимПроведения), значения реквизитов документа (ЭтотОбъект), результаты запросов и условия. Это позволит точно определить, на каком этапе происходит ошибка или почему условие запрета не срабатывает.
Запомните:
&Перед для установки Отказ = Истина.(Отказ, РежимЗаписи, РежимПроведения) для процедуры ПередЗаписью.ЭтотОбъект или временные таблицы в запросах.Отказ = Истина; в сочетании с ВызватьИсключение для надежного запрета и информирования пользователя.Следуя этим рекомендациям, вы сможете эффективно и надежно реализовать запрет записи и проведения документов с помощью расширений в 1С.