Как перевести код записи в регистр и диалог подтверждения на управляемые формы в режиме «Такси»?

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

Переход со старых обычных форм «1С:Предприятия 7.7» или «1С:Предприятие 8» (обычное приложение) на современные управляемые формы в режиме «Такси» часто вызывает трудности у начинающих разработчиков. Основная сложность заключается в кардинальном изменении парадигмы работы интерфейса: управляемый интерфейс требует полного отказа от модальных окон и выполнения тяжелых операций строго на стороне сервера.

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

---

В чем заключаются основные проблемы старого кода?

Проанализируем исходный код, представленный автором темы, и выделим критические архитектурные ошибки:

  1. Использование модального метода Вопрос() в транзакции записи: В режиме работы «Без модальности» (стандарт для режима «Такси») вызов метода Вопрос() приведет к аварийному завершению работы программы. Платформа требует использования асинхронных методов.
  2. Попытка приостановить запись объекта: Обработчик события ПередЗаписью на клиенте ожидает мгновенного возврата булевого флага в параметр Отказ. Мы не можем «заморозить» выполнение кода на клиенте, пока пользователь думает над вопросом, так как асинхронный вызов возвращает управление немедленно.
  3. Критическая ошибка работы с регистром сведений: Использование метода СоздатьНаборЗаписей() без установки отборов перед вызовом Записать(Истина) (или при попадании в блок Исключение) полностью очистит весь регистр сведений! Это одна из самых опасных и частых ошибок начинающих разработчиков.
  4. Неправильный контекст выполнения: Непосредственная запись в базу данных из процедур формы может привести к нарушению транзакционной целостности. Если запись самого элемента справочника сорвется, данные в регистре сведений уже будут изменены вручную, что вызовет рассинхронизацию данных.

Рассмотрим по шагам, как исправить каждую из этих проблем в соответствии со стандартами разработки фирмы «1С».

---

Шаг 1. Реализация асинхронного вопроса пользователю на клиенте

Для реализации немодального диалога мы применим паттерн с прерыванием стандартной записи и повторным программным вызовом метода Записать(). Суть метода заключается в следующем:

Разберем пример реализации данного алгоритма в модуле управляемой формы справочника:


&НаКлиенте
Процедура ПередЗаписью(Отказ, ПараметрыЗаписи)
    
    // Проверяем, не была ли уже пройдена проверка ранее.
    // Это предотвратит бесконечную рекурсию при повторном программном вызове записи.
    Если ПараметрыЗаписи.Свойство("ПроверкаИнтерактивноВыполнена") Тогда
        Возврат; 
    КонецЕсли;
    
    // Отменяем текущую интерактивную запись, чтобы дождаться ответа пользователя
    Отказ = Истина;
    
    // Формируем текст вопроса и параметры для обработки ответа
    ТекстВопроса = "Записать значения сотрудников на " + Формат(Объект.ДатаДанных, "ДФ=dd.MM.yyyy") + " г.?" 
                 + Символы.ПС + "Внимание: если не было кадровых изменений, записывать не рекомендуется!";
                 
    // Создаем описание оповещения, которое сработает после того, как пользователь нажмет кнопку в диалоге
    ПараметрыОповещения = Новый Структура("ПараметрыЗаписи", ПараметрыЗаписи);
    Оповещение = Новый ОписаниеОповещения("ПослеОтветаНаВопросЗаписать", ЭтотОбъект, ПараметрыОповещения);
    
    // Показываем немодальный диалог
    ПоказатьВопрос(Оповещение, ТекстВопроса, РежимДиалогаВопрос.ДаНет, , КодВозвратаДиалога.Нет, "Внимание!!!");
    
КонецПроцедуры

&НаКлиенте
Процедура ПослеОтветаНаВопросЗаписать(РезультатВопроса, ДополнительныеПараметры) Экспорт
    
    // Если пользователь ответил утвердительно "Да"
    Если РезультатВопроса = КодВозвратаДиалога.Да Тогда
        
        // Восстанавливаем исходные параметры записи
        ПараметрыЗаписи = ДополнительныеПараметры.ПараметрыЗаписи;
        
        // Добавляем маркеры: один для обхода повторного вопроса, второй — для выполнения записи на сервере
        ПараметрыЗаписи.Вставить("ПроверкаИнтерактивноВыполнена", Истина);
        ПараметрыЗаписи.Вставить("ВыполнитьЗаписьВРегистр", Истина);
        
        // Повторно вызываем запись объекта формы
        ЭтотОбъект.Записать(ПараметрыЗаписи);
        
    КонецЕсли;
    
КонецПроцедуры

---

Шаг 2. Безопасная запись в регистр сведений

Выясним причину, почему исходный код автора был опасен. Метод НаборЗаписей.Записать(Истина) без установленных отборов полностью очищает всю таблицу регистра в базе данных перед записью новых элементов. Если у вас в регистре было 10 000 записей по разным организациям, после выполнения такого кода останется только одна — текущая.

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

Вариант А. Использование Менеджера Записи (Рекомендуется для одиночных записей)

Если в регистре сведений под каждую комбинацию измерений и периода пишется ровно одна строка, наиболее простым и безопасным решением будет использование объекта РегистрСведенийМенеджерЗаписи. Он работает точечно и автоматически выполняет операцию «Создать новую запись или обновить существующую»:


Процедура ЗаписатьВРегБезопасно(ИзмерениеСотрудник, ИмяИзмерения, ИмяРесурса, ДатаДанных)
    
    Если ЗначениеНеЗаполнено(ИзмерениеСотрудник) Тогда
        Возврат;
    КонецЕсли;
    
    // Инициализируем точечный менеджер записи
    МЗ = РегистрыСведений.Организация.СоздатьМенеджерЗаписи();
    МЗ.Период = ДатаДанных;
    МЗ.Измерение = ИмяИзмерения; // Установка ключевого поля (измерения)
    
    // Заполняем ресурс динамически
    МЗ[ИмяРесурса] = ИзмерениеСотрудник;
    
    Попытка
        МЗ.Записать(); // Автоматически перезапишет данные по этому ключу, не трогая другие записи
    Исключение
        // Обязательно логируем ошибку в Журнал Регистрации для последующего анализа администратором
        ЗаписьЖурналаРегистрации("РегистрацияСотрудников.ОшибкаЗаписи", 
            УровеньЖурналаРегистрации.Ошибка, , , ПодробноеПредставлениеОшибки(ИнформацияОбОшибке()));
        ВызватьИсключение; // Передаем ошибку дальше, чтобы отменить транзакцию записи справочника
    КонецПопытки;
    
КонецПроцедуры

Вариант Б. Использование Набора Записей с обязательным отбором

Если специфика логики требует работы именно с набором записей, вы обязаны жестко ограничить область его действия с помощью фильтров (отборов). В этом случае будут перезаписаны только те строки, которые соответствуют заданному условию:


Набор = РегистрыСведений.Организация.СоздатьНаборЗаписей();

// Установка жесткого отбора по ключевым полям обязательна!
Набор.Отбор.Период.Установить(ДатаДанных);
Набор.Отбор.Измерение.Установить(ИмяИзмерения);

НоваяСтрока = Набор.Добавить();
НоваяСтрока.Период = ДатаДанных;
НоваяСтрока.Измерение = ИмяИзмерения;
НоваяСтрока[ИмяРесурса] = ИзмерениеСотрудник;

// Метод Записать(Истина) очистит и перезапишет данные ТОЛЬКО в пределах установленного отбора
Набор.Записать(Истина); 

---

Шаг 3. Перенос бизнес-логики в транзакцию записи объекта (Модуль объекта)

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

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

Рассмотрим, как связать форму и модуль объекта по шагам.

Шаг 3.1. Передача признака записи из формы на сервер

В модуле формы справочника пропишем обработчик события ПередЗаписьюНаСервере, который перенесет наш флаг согласия пользователя из параметров формы во внутренние свойства записываемого объекта:


&НаСервере
Процедура ПередЗаписьюНаСервере(Отказ, ТекущийОбъект, ПараметрыЗаписи)
    
    // Если на клиенте пользователь согласился записать сведения, передаем этот флаг в свойства объекта
    Если ПараметрыЗаписи.Свойство("ВыполнитьЗаписьВРегистр") Тогда
        ТекущийОбъект.ДополнительныеСвойства.Вставить("ВыполнитьЗаписьВРегистр", Истина);
    КонецЕсли;
    
КонецПроцедуры

Шаг 3.2. Обработка записи в модуле объекта Справочника

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


Процедура ПриЗаписи(Отказ)
    
    // Проверяем наличие флага, переданного из управляемой формы
    Если ДополнительныеСвойства.Свойство("ВыполнитьЗаписьВРегистр") Тогда
        
        // Все действия выполняются внутри одной транзакции. 
        // Если здесь произойдет сбой, запись самого справочника также будет отменена.
        ЗаписатьВРегБезопасно(ГлБухгалтер, "ГлБухгалтер", "Значение", ДатаДанных);
        ЗаписатьВРегБезопасно(Кассир, "Кассир", "Значение", "Значение", ДатаДанных);
        ЗаписатьВРегБезопасно(МинимальнаяЗарплата, "МинимальнаяЗарплата", "ЗначениеЧисло", ДатаДанных);
        ЗаписатьВРегБезопасно(МинТариф, "МинТариф", "ЗначениеЧисло", ДатаДанных);
        ЗаписатьВРегБезопасно(Руководитель, "Руководитель", "Значение", ДатаДанных);
        ЗаписатьВРегБезопасно(БухгалтерПоПродаже, "БухПродаж", "Значение", ДатаДанных);
        ЗаписатьВРегБезопасно(МенеджерПоПродажам, "МенеджерПоПродажам", "Значение", ДатаДанных);
        
    КонецЕсли;
    
КонецПроцедуры

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

---

Резюме и выводы

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

← На главную