Как правильно валидировать данные и предотвратить сохранение документа в 1С 7.7 при закрытии формы?

Программист 1С v7.7
← На главную

При работе с документами в 1С 7.7 мы часто сталкиваемся с необходимостью контролировать процесс сохранения и закрытия формы. Особенно актуальной эта задача становится, когда необходимо выполнить определенные проверки данных перед записью документа в базу. В случае обнаружения ошибок, нам важно не только сообщить об этом пользователю, но и предотвратить как сохранение некорректного документа, так и его закрытие до исправления.

Представим типовую ситуацию: мы закрываем форму документа, например, нажатием на "крестик" в правом верхнем углу окна. Система, обнаружив модификацию документа, штатно предлагает нам сохранить изменения, выводя диалог "Сохранить изменения? (Да/Нет/Отмена)". Если мы нажимаем "Да", документ, казалось бы, должен сохраниться, и форма закроется. Однако, если мы хотим добавить свои условия валидации – например, проверить, нет ли пустых или дублирующихся полей – и предотвратить сохранение при их наличии, возникает вопрос: в какой процедуре эти проверки выполнять и как управлять системным поведением?

Часто программисты по ошибке пытаются реализовать эту логику в процедуре ПриЗакрытии() документа, используя СтатусВозврата(0) для предотвращения закрытия формы. Однако, как мы обнаружим, это не решает проблему полностью, так как документ может быть уже сохранен до того, как сработает ПриЗакрытии().

Последовательность выполнения процедур при закрытии формы документа

Давайте разберемся, в какой последовательности система 1С 7.7 вызывает предопределенные процедуры при попытке закрыть форму документа, которая была модифицирована:

  1. Системный диалог "Сохранить изменения? (Да/Нет/Отмена)": Это первое, что видит пользователь, если форма документа была изменена.
  2. Если пользователь выбрал "Да" (сохранить):
    1. Вызывается процедура ПриЗаписи(). Это критически важный этап, так как именно здесь происходит основная валидация данных перед их фактической записью в базу. Если ПриЗаписи() завершается успешно (т.е. не возвращает 0 через СтатусВозврата()), платформа выполняет запись документа.
    2. После успешной записи или если ПриЗаписи() не отменила операцию, вызывается процедура ПриЗакрытии().
  3. Если пользователь выбрал "Нет" (не сохранять):
    1. Процедура ПриЗаписи() не вызывается, так как сохранение отменено.
    2. Сразу вызывается процедура ПриЗакрытии().
  4. Если пользователь выбрал "Отмена":
    1. Ни ПриЗаписи(), ни ПриЗакрытии() не вызываются, и форма остается открытой.

Важно отметить, что процедура ОбработкаПроведения() не имеет прямого отношения к простому сохранению или закрытию формы. Она вызывается только тогда, когда документ явно проводится, например, при нажатии на кнопку "Провести" или "Провести и закрыть", а не при простом сохранении.

ПриЗаписи(): Наш главный инструмент для валидации и предотвращения сохранения

Процедура ПриЗаписи() является идеальным местом для выполнения всех проверок данных, которые должны предотвращать сохранение некорректного документа. Эта процедура срабатывает непосредственно перед тем, как 1С попытается записать изменения документа в базу данных. Здесь мы можем проверить:

Использование СтатусВозврата(0) в ПриЗаписи()

Если в ПриЗаписи() мы обнаруживаем ошибку, мы должны немедленно сообщить об этом пользователю и установить СтатусВозврата(0). Что это дает:

  1. Отмена записи документа: Система прекратит попытку записи документа в базу данных. Это ключевой момент – документ не появится в журнале, если он некорректен.
  2. Предотвращение закрытия формы: Если запись отменена, форма документа также не закроется, оставляя пользователю возможность исправить ошибки.

По умолчанию, если ПриЗаписи() завершается без вызова СтатусВозврата(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); // Это просто не даст форме закрыться, но не отменит уже произошедшую запись
КонецПроцедуры

Как мы уже выяснили, если ПриЗаписи() отработает и документ будет сохранен, то эта строка лишь заставит форму остаться открытой, но в журнале документ уже появится.

Пошаговое решение проблемы валидации и контроля сохранения

Теперь, когда мы понимаем последовательность вызовов процедур и роль СтатусВозврата() в каждой из них, давайте сформулируем правильный подход к решению задачи:

  1. Всю основную логику валидации помещаем в процедуру ПриЗаписи()

    Именно здесь должны находиться все проверки, которые определяют, можно ли сохранять документ или нет. Это гарантирует, что проверки будут выполнены *до* фактической записи данных в базу.

  2. При обнаружении ошибки в ПриЗаписи() немедленно вызываем СтатусВозврата(0) и Возврат

    Это мгновенно прерывает операцию записи документа и предотвращает закрытие формы. Пользователь получит сообщение об ошибке и сможет ее исправить, не рискуя сохранить некорректный документ.

    
    Если ПустоеЗначение(КодМаркировки) = 1 Тогда
        Сообщить("Ошибка: Код маркировки не заполнен!");
        СтатусВозврата(0); // Останавливаем запись
        Возврат;           // Выходим из процедуры
    КонецЕсли;
    
  3. Избегаем явного вызова Записать() внутри ПриЗаписи(), если нет особой необходимости

    В большинстве случаев платформа сама выполнит запись документа, если ПриЗаписи() завершилась успешно (то есть, не был вызван СтатусВозврата(0)). Явный вызов Записать() может привести к нежелательным побочным эффектам или повторной записи. Однако, если такая необходимость все же есть (например, для взаимодействия с внешними системами или файлами, которые должны быть синхронизированы *сразу после* записи документа в базу), оберните его в блок Попытка...Исключение.

  4. Используем ПриЗакрытии() только для действий, выполняемых *после* записи или ее отмены

    Если вам нужно выполнить какие-либо действия (например, очистка временных данных, запрос подтверждения) после того, как документ был успешно сохранен или отменено его сохранение, используйте ПриЗакрытии(). Установка СтатусВозврата(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.

← На главную