Как запретить запись документа с помеченным на удаление договором в обычной форме

Программист 1С v8.3 (Обычные формы) IT и автоматизация бизнеса
← На главную

При работе с обычными (неуправляемыми) формами в 1С:Предприятие 8 начинающие разработчики часто сталкиваются с вопросами разграничения контекста исполнения кода и правильного обращения к данным, которые еще не сохранены в базу. В этой статье мы подробно разберем, как реализовать проверку реквизитов на "пометку удаления" перед записью документа, проанализируем типичные ошибки при работе с табличными частями и выясним, где именно лучше располагать логику запрета.

Контекст исполнения кода в обычных формах

В отличие от управляемого приложения, где мы явно указываем директивы &НаКлиенте или &НаСервере, в модуле обычной формы код выполняется в контексте толстого клиента. Это означает, что мы имеем прямой доступ к объекту, но при обращении к свойствам ссылочных типов (например, через точку от договора) платформа может выполнять неявные обращения к базе данных.

Рассмотрим ситуацию: пользователь выбрал договор в поле ввода. Если мы напишем ДоговорКонтрагента.ПометкаУдаления, система обратится к ссылке, которая уже находится в базе, и получит её текущее состояние. Проблема "клиент-сервер" в обычных формах часто надуманна, так как основной вопрос заключается не в месте исполнения, а в том, какие данные мы анализируем: те, что уже записаны, или те, что пользователь только что выбрал на форме.

Реализация проверки в обработчике ПередЗаписью

Для того чтобы запретить запись документа при определенных условиях, стандартно используется событие ПередЗаписью. Проанализируем пример кода, который часто пытаются использовать новички:


Процедура ПередЗаписью(Отказ, РежимЗаписи, РежимПроведения)
    Если ДоговорКонтрагента.ПометкаУдаления = Истина Тогда
        Сообщить("Запрещено выбирать договор, помеченный на удаление");
        Отказ = Истина;
    КонецЕсли;
КонецПроцедуры

Несмотря на кажущуюся простоту, здесь есть несколько важных моментов:

  1. Избыточность проверки: Условие = Истина для логических полей (Булево) является лишним. Профессиональнее писать Если ДоговорКонтрагента.ПометкаУдаления Тогда.
  2. Место проверки: Если логика должна работать всегда (и при ручном вводе, и при загрузке данных), её следует выносить в Модуль объекта. Если же проверка нужна только для интерактивных действий пользователя, она остается в Модуле формы.
  3. Объект или Ссылка: Если вы проверяете реквизит самого документа, который редактируете, помните, что обращение ДоговорКонтрагента вернет текущее значение из оперативной памяти (то, что ввел пользователь), а обращение Ссылка.ДоговорКонтрагента — то, что было сохранено в базе до начала редактирования.

Работа с табличными частями: типичные ошибки

Часто реквизиты (например, договоры или контрагенты) хранятся не в "шапке" документа, а в табличной части (например, в расшифровке платежа для кассовых ордеров). Рассмотрим ошибку, которую часто совершают при попытке быстрой проверки:


// ОШИБОЧНЫЙ ПРИМЕР
Если ЭтотОбъект.РасшифровкаПлатежа[0].ДоговорКонтрагента.ПометкаУдаления Тогда
    // Логика отказа
КонецЕсли;

Проанализируем ситуацию: обращение через индекс [0] крайне опасно. Если табличная часть РасшифровкаПлатежа окажется пустой, программа аварийно завершится с ошибкой "Индекс находится за границей массива". Более того, такая проверка затронет только первую строку, игнорируя остальные.

Правильный обход табличной части

Для корректной проверки всех строк необходимо использовать цикл. Разберем правильный алгоритм действий:


Процедура ПередЗаписью(Отказ, РежимЗаписи, РежимПроведения)
    
    Для Каждого ТекущаяСтрока Из РасшифровкаПлатежа Цикл
        
        // Проверяем, заполнена ли ссылка, прежде чем обращаться к её свойствам
        Если ЗначениеЗаполнено(ТекущаяСтрока.ДоговорКонтрагента) Тогда
            
            Если ТекущаяСтрока.ДоговорКонтрагента.ПометкаУдаления Тогда
                Сообщить("В строке № " + ТекущаяСтрока.НомерСтроки + " выбран договор, помеченный на удаление!");
                Отказ = Истина;
                // Если нашли ошибку, можно прервать цикл
                Прервать; 
            КонецЕсли;
            
        КонецЕсли;
        
    КонецЦикла;
    
КонецПроцедуры

Оптимизация производительности

Рассмотрим ситуацию с точки зрения быстродействия. Если в табличной части 100 строк, обращение через точку ТекущаяСтрока.ДоговорКонтрагента.ПометкаУдаления породит 100 отдельных запросов к базе данных. В 1С это называется "проблемой N+1 запроса".

Чтобы оптимизировать этот процесс, мы можем собрать все уникальные договоры в массив и проверить их одним запросом к базе данных. Это особенно актуально для высоконагруженных систем или при работе через медленные каналы связи.


Запрос = Новый Запрос;
Запрос.Текст = 
    "ВЫБРАТЬ РАЗЛИЧНЫЕ
    |   ТЧ.ДоговорКонтрагента КАК Договор
    |ПОМЕСТИТЬ ВТ_Договоры
    |ИЗ
    |   &ТЧ КАК ТЧ
    |;
    |ВЫБРАТЬ
    |   Договоры.Ссылка
    |ИЗ
    |   Справочник.ДоговорыКонтрагентов КАК Договоры
    |ГДЕ
    |   Договоры.Ссылка В (ВЫБРАТЬ Договор ИЗ ВТ_Договоры)
    |   И Договоры.ПометкаУдаления = ИСТИНА";

Запрос.УстановитьПараметр("ТЧ", РасшифровкаПлатежа.Выгрузить());
Результат = Запрос.Выполнить();

Если Не Результат.Пустой() Тогда
    Отказ = Истина;
    Сообщить("В документе присутствуют помеченные на удаление договоры!");
КонецЕсли;

Предотвращение выбора (User Experience)

Помимо проверки при записи, хорошим тоном считается запрет выбора такого договора еще на этапе подбора. Для этого в свойствах поля ввода на форме или в свойствах реквизита объекта можно настроить Параметры выбора. Установите отбор: ПометкаУдаления — Ложь. Таким образом, пользователь просто не увидит помеченные объекты в списке выбора, и необходимость в жестких проверках ПередЗаписью станет лишь подстраховкой для программных манипуляций.

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

← На главную