При работе с документами в 1С 7.7 мы часто сталкиваемся с необходимостью контролировать процесс сохранения и закрытия формы. Особенно актуальной эта задача становится, когда необходимо выполнить определенные проверки данных перед записью документа в базу. В случае обнаружения ошибок, нам важно не только сообщить об этом пользователю, но и предотвратить как сохранение некорректного документа, так и его закрытие до исправления.
Представим типовую ситуацию: мы закрываем форму документа, например, нажатием на "крестик" в правом верхнем углу окна. Система, обнаружив модификацию документа, штатно предлагает нам сохранить изменения, выводя диалог "Сохранить изменения? (Да/Нет/Отмена)". Если мы нажимаем "Да", документ, казалось бы, должен сохраниться, и форма закроется. Однако, если мы хотим добавить свои условия валидации – например, проверить, нет ли пустых или дублирующихся полей – и предотвратить сохранение при их наличии, возникает вопрос: в какой процедуре эти проверки выполнять и как управлять системным поведением?
Часто программисты по ошибке пытаются реализовать эту логику в процедуре ПриЗакрытии() документа, используя СтатусВозврата(0) для предотвращения закрытия формы. Однако, как мы обнаружим, это не решает проблему полностью, так как документ может быть уже сохранен до того, как сработает ПриЗакрытии().
Давайте разберемся, в какой последовательности система 1С 7.7 вызывает предопределенные процедуры при попытке закрыть форму документа, которая была модифицирована:
ПриЗаписи(). Это критически важный этап, так как именно здесь происходит основная валидация данных перед их фактической записью в базу. Если ПриЗаписи() завершается успешно (т.е. не возвращает 0 через СтатусВозврата()), платформа выполняет запись документа.ПриЗаписи() не отменила операцию, вызывается процедура ПриЗакрытии().ПриЗаписи() не вызывается, так как сохранение отменено.ПриЗакрытии().ПриЗаписи(), ни ПриЗакрытии() не вызываются, и форма остается открытой.Важно отметить, что процедура ОбработкаПроведения() не имеет прямого отношения к простому сохранению или закрытию формы. Она вызывается только тогда, когда документ явно проводится, например, при нажатии на кнопку "Провести" или "Провести и закрыть", а не при простом сохранении.
ПриЗаписи(): Наш главный инструмент для валидации и предотвращения сохраненияПроцедура ПриЗаписи() является идеальным местом для выполнения всех проверок данных, которые должны предотвращать сохранение некорректного документа. Эта процедура срабатывает непосредственно перед тем, как 1С попытается записать изменения документа в базу данных. Здесь мы можем проверить:
СтатусВозврата(0) в ПриЗаписи()Если в ПриЗаписи() мы обнаруживаем ошибку, мы должны немедленно сообщить об этом пользователю и установить СтатусВозврата(0). Что это дает:
По умолчанию, если ПриЗаписи() завершается без вызова СтатусВозврата(0) (или с СтатусВозврата(1)), платформа считает, что операция записи прошла успешно, и выполняет запись документа в базу.
Рассмотрим пример кода, демонстрирующий, как использовать ПриЗаписи() для валидации:
Процедура ПриЗаписи()
Перем Сохранить;
Сохранить = 1; // Изначально предполагаем, что документ можно сохранить
ВыбратьСтроки();
Пока ПолучитьСтроку() = 1 Цикл
// Проверяем каждую строку на наличие пустых кодов маркировки
Если ПустоеЗначение(КодМаркировки) = 1 Тогда
Сообщить("В строке №: " + НомерСтроки + " " + "не внесен код маркировки. Документ не будет сохранен!");
Сохранить = 0; // Указываем, что документ сохранять нельзя
// ВАЖНО: При обнаружении ошибки, немедленно возвращаем СтатусВозврата(0)
// и выходим из процедуры, чтобы остановить запись и закрытие формы.
СтатусВозврата(0);
Возврат;
КонецЕсли;
КонецЦикла;
// Если мы дошли до этого места, значит, все строки прошли проверку
Если Сохранить = 1 Тогда
// Здесь мы можем выполнить дополнительные действия, которые должны произойти
// только после того, как мы убедились в корректности данных и платформа
// гарантированно запишет документ.
// Если вам нужно сохранить данные во внешний файл *после* записи в базу,
// но *до* того, как платформа зафиксирует транзакцию, можно вызвать Записать() явно.
// Однако, обычно это не требуется, так как платформа сама запишет документ.
// Если вызов Записать() необходим, обернем его в Попытка-Исключение.
Попытка
Записать(); // Явный вызов записи, если это необходимо для логики
ЗначениеВФайл(КаталогИБ()+"SaveMark\"+ДокОснование.НомерДок+ДокОснование.ДатаДок+"Mark.dat",ИтогТЗ);
Исключение
Сообщить("Ошибка при записи документа или сохранении файла: " + ОписаниеОшибки());
СтатусВозврата(0); // Отменяем операцию, если Записать() вызвала ошибку
Возврат;
КонецПопытки;
КонецЕсли;
// Если все успешно, СтатусВозврата() останется 1 по умолчанию, и документ будет записан,
// а форма закроется, если не было других препятствий.
// Можно явно указать СтатусВозврата(Сохранить), но если 'Сохранить' равно 1,
// это избыточно, т.к. 1 - значение по умолчанию.
КонецПроцедуры
В приведенном примере мы видим, что переменная Сохранить используется для флага, но ключевым является вызов СтатусВозврата(0) непосредственно после сообщения об ошибке. Это немедленно прерывает процесс записи и предотвращает закрытие формы.
Автор исходной темы столкнулся с проблемой, когда документ все равно записывался в журнал, хотя форма не закрывалась. Причина в том, что в его первоначальном коде, при обнаружении пустых строк, переменная Сохранить устанавливалась в 0, но не было немедленного вызова СтатусВозврата(0) и Возврат. Следовательно, процедура ПриЗаписи() продолжала выполняться до конца, и если явный вызов Записать() не происходил при Сохранить = 0, платформа все равно считала ПриЗаписи() успешной (потому что СтатусВозврата(0) не был вызван) и сохраняла документ.
Еще один важный момент: в большинстве случаев нет необходимости явно вызывать Записать() внутри процедуры ПриЗаписи(). Платформа 1С сама запишет документ в базу данных, если ПриЗаписи() завершится без установки СтатусВозврата(0). Явный вызов Записать() может быть оправдан, если вам нужно выполнить какие-то действия (например, запись данных во внешний файл) *сразу после* того, как документ будет записан в базу, но *до* окончательного завершения транзакции записи платформой. В таких случаях, как показано выше, рекомендуется обернуть вызов Записать() в блок Попытка...Исключение для обработки возможных ошибок.
ПриЗакрытии(): Ее роль и ограниченияПроцедура ПриЗакрытии() срабатывает непосредственно перед закрытием формы документа, независимо от того, был ли документ сохранен или нет. Ее назначение – выполнение действий, которые должны произойти *после* завершения операции записи (или ее отмены).
СтатусВозврата(0) в ПриЗакрытии()Если в ПриЗакрытии() установить СтатусВозврата(0), это предотвратит закрытие формы документа. Однако есть критическое различие с ПриЗаписи():
ПриЗаписи() завершилась успешно (т.е. документ был записан в базу), то ПриЗакрытии() будет вызвана уже после того, как документ сохранен.СтатусВозврата(0) в ПриЗакрытии() лишь не позволит форме закрыться, но не отменит уже выполненную запись документа. Документ будет находиться в журнале, несмотря на то, что форма останется открытой. Это именно та проблема, с которой столкнулся автор темы.Таким образом, ПриЗакрытии() не подходит для логики, которая должна предотвращать *саму запись* некорректного документа. Ее лучше использовать для:
Пример (неправильного) использования ПриЗакрытии() из темы:
Процедура ПриЗакрытии()
СтатусВозврата(0); // Это просто не даст форме закрыться, но не отменит уже произошедшую запись
КонецПроцедуры
Как мы уже выяснили, если ПриЗаписи() отработает и документ будет сохранен, то эта строка лишь заставит форму остаться открытой, но в журнале документ уже появится.
Теперь, когда мы понимаем последовательность вызовов процедур и роль СтатусВозврата() в каждой из них, давайте сформулируем правильный подход к решению задачи:
ПриЗаписи()Именно здесь должны находиться все проверки, которые определяют, можно ли сохранять документ или нет. Это гарантирует, что проверки будут выполнены *до* фактической записи данных в базу.
ПриЗаписи() немедленно вызываем СтатусВозврата(0) и ВозвратЭто мгновенно прерывает операцию записи документа и предотвращает закрытие формы. Пользователь получит сообщение об ошибке и сможет ее исправить, не рискуя сохранить некорректный документ.
Если ПустоеЗначение(КодМаркировки) = 1 Тогда
Сообщить("Ошибка: Код маркировки не заполнен!");
СтатусВозврата(0); // Останавливаем запись
Возврат; // Выходим из процедуры
КонецЕсли;
Записать() внутри ПриЗаписи(), если нет особой необходимостиВ большинстве случаев платформа сама выполнит запись документа, если ПриЗаписи() завершилась успешно (то есть, не был вызван СтатусВозврата(0)). Явный вызов Записать() может привести к нежелательным побочным эффектам или повторной записи. Однако, если такая необходимость все же есть (например, для взаимодействия с внешними системами или файлами, которые должны быть синхронизированы *сразу после* записи документа в базу), оберните его в блок Попытка...Исключение.
ПриЗакрытии() только для действий, выполняемых *после* записи или ее отменыЕсли вам нужно выполнить какие-либо действия (например, очистка временных данных, запрос подтверждения) после того, как документ был успешно сохранен или отменено его сохранение, используйте ПриЗакрытии(). Установка СтатусВозврата(0) здесь только не даст форме закрыться, но не изменит статус сохраненного документа.
Давайте посмотрим на окончательный, корректный вариант кода, который эффективно решает поставленную задачу, используя механизмы 1С 7.7:
//******************************************************************
// Процедура ПриЗаписи()
// Вызывается перед записью документа в базу данных.
// Используется для валидации данных и предотвращения сохранения некорректного документа.
//******************************************************************
Процедура ПриЗаписи()
Перем ОбнаруженыОшибки;
ОбнаруженыОшибки = 0;
// Инициируем проверку строк табличной части документа
ВыбратьСтроки();
Пока ПолучитьСтроку() = 1 Цикл
// Проверяем каждую строку на заполнение обязательного реквизита "КодМаркировки"
Если ПустоеЗначение(КодМаркировки) = 1 Тогда
Сообщить("В строке №: " + НомерСтроки + " не внесен код маркировки. Пожалуйста, исправьте ошибку.");
ОбнаруженыОшибки = 1; // Устанавливаем флаг обнаружения ошибок
Прервать; // Выходим из цикла, так как ошибка уже найдена
КонецЕсли;
// Здесь можно добавить другие проверки, например, на дублирование значений
// в текущей строке или по всему документу
// Пример проверки на дубли:
// Если ПроверитьДубльКодаМаркировки(КодМаркировки, НомерСтроки) = 0 Тогда
// Сообщить("Код маркировки '" + КодМаркировки + "' в строке №: " + НомерСтроки + " уже встречается в документе.");
// ОбнаруженыОшибки = 1;
// Прервать;
// КонецЕсли;
КонецЦикла;
// Если обнаружены ошибки при валидации
Если ОбнаруженыОшибки = 1 Тогда
// Критически важно: устанавливаем СтатусВозврата(0), чтобы отменить запись документа.
// Это также предотвратит закрытие формы.
СтатусВозврата(0);
Возврат; // Выходим из процедуры, чтобы дальнейший код не выполнялся
КонецЕсли;
// Если ошибок не обнаружено (ОбнаруженыОшибки = 0), значит, документ корректен.
// Теперь можем выполнить дополнительные действия, которые должны произойти
// только при успешной записи.
// Если вам нужно сохранить данные во внешний файл *после* того, как документ
// будет записан в базу, но *до* окончательного завершения операции записи платформой,
// можно явно вызвать Записать().
// Однако, в большинстве случаев платформа сама выполняет Записать(),
// если ПриЗаписи() завершается со СтатусВозврата(1) или без него.
// В данном примере мы предполагаем, что Записать() нужно вызвать явно
// для сохранения внешнего файла, и обернем это в Попытка-Исключение.
Попытка
Записать(); // Явный вызов записи документа в базу
// После успешной записи в базу, сохраняем данные во внешний файл
ЗначениеВФайл(КаталогИБ()+"SaveMark\"+ДокОснование.НомерДок+ДокОснование.ДатаДок+"Mark.dat",ИтогТЗ);
// Если все прошло успешно, СтатусВозврата останется 1 по умолчанию,
// и документ будет записан, а форма закроется.
Исключение
// Если при явном вызове Записать() или сохранении файла произошла ошибка
Сообщить("Произошла ошибка при сохранении документа или данных во внешний файл: " + ОписаниеОшибки());
СтатусВозврата(0); // Отменяем операцию, чтобы документ не был записан
Возврат;
КонецПопытки;
// Если мы дошли до этого места, значит, документ успешно прошел все проверки
// и был записан. СтатусВозврата останется 1 (по умолчанию), что позволит форме закрыться.
КонецПроцедуры
//******************************************************************
// Процедура ПриЗакрытии()
// Вызывается непосредственно перед закрытием формы документа.
// Используется для действий после записи/отмены записи, но не для предотвращения записи.
//******************************************************************
Процедура ПриЗакрытии()
// В этой процедуре НЕ следует размещать логику, которая должна
// предотвращать запись документа. Эта логика должна быть в ПриЗаписи().
// Если здесь установить СтатусВозврата(0), форма не закроется,
// но если ПриЗаписи() уже успешно сохранила документ, он останется записанным.
// Пример использования ПриЗакрытии:
// Если Документ.Проведен() = 1 Тогда
// Сообщить("Документ успешно проведен и закрывается.");
// Иначе
// Сообщить("Документ закрывается. Изменения не были сохранены или были отменены.");
// КонецЕсли;
// Если нет специфических задач для ПриЗакрытии, ее можно оставить пустой.
// Или использовать для освобождения ресурсов, очистки временных данных и т.п.
КонецПроцедуры
Мы выяснили, что для эффективной валидации данных и надежного предотвращения сохранения некорректных документов в 1С 7.7, нам необходимо активно использовать процедуру ПриЗаписи(). Установка СтатусВозврата(0) в ПриЗаписи() при обнаружении ошибок является ключевым элементом, который отменяет операцию записи и удерживает форму открытой для исправлений.
Процедура ПриЗакрытии(), в свою очередь, предназначена для выполнения действий уже после того, как решение о сохранении или отмене было принято и, возможно, выполнено. Использование СтатусВозврата(0) в ПриЗакрытии() лишь предотвратит закрытие формы, но не отменит уже состоявшуюся запись документа. Понимая эту последовательность и назначение каждой процедуры, мы можем создавать более надежные и удобные для пользователя решения в 1С 7.7.