Переход со старых обычных форм «1С:Предприятия 7.7» или «1С:Предприятие 8» (обычное приложение) на современные управляемые формы в режиме «Такси» часто вызывает трудности у начинающих разработчиков. Основная сложность заключается в кардинальном изменении парадигмы работы интерфейса: управляемый интерфейс требует полного отказа от модальных окон и выполнения тяжелых операций строго на стороне сервера.
В этой статье мы подробно разберем, как правильно переписать устаревший синхронный код, содержащий интерактивные вопросы пользователю и прямую запись в регистры сведений, под требования современной платформы «1С:Предприятие 8.3» без использования модальности и режима совместимости.
---
Проанализируем исходный код, представленный автором темы, и выделим критические архитектурные ошибки:
Вопрос() в транзакции записи: В режиме работы «Без модальности» (стандарт для режима «Такси») вызов метода Вопрос() приведет к аварийному завершению работы программы. Платформа требует использования асинхронных методов.ПередЗаписью на клиенте ожидает мгновенного возврата булевого флага в параметр Отказ. Мы не можем «заморозить» выполнение кода на клиенте, пока пользователь думает над вопросом, так как асинхронный вызов возвращает управление немедленно.СоздатьНаборЗаписей() без установки отборов перед вызовом Записать(Истина) (или при попадании в блок Исключение) полностью очистит весь регистр сведений! Это одна из самых опасных и частых ошибок начинающих разработчиков.Рассмотрим по шагам, как исправить каждую из этих проблем в соответствии со стандартами разработки фирмы «1С».
---
Для реализации немодального диалога мы применим паттерн с прерыванием стандартной записи и повторным программным вызовом метода Записать(). Суть метода заключается в следующем:
Отказ = Истина, прерывая процесс.ПоказатьВопрос().Разберем пример реализации данного алгоритма в модуле управляемой формы справочника:
&НаКлиенте
Процедура ПередЗаписью(Отказ, ПараметрыЗаписи)
// Проверяем, не была ли уже пройдена проверка ранее.
// Это предотвратит бесконечную рекурсию при повторном программном вызове записи.
Если ПараметрыЗаписи.Свойство("ПроверкаИнтерактивноВыполнена") Тогда
Возврат;
КонецЕсли;
// Отменяем текущую интерактивную запись, чтобы дождаться ответа пользователя
Отказ = Истина;
// Формируем текст вопроса и параметры для обработки ответа
ТекстВопроса = "Записать значения сотрудников на " + Формат(Объект.ДатаДанных, "ДФ=dd.MM.yyyy") + " г.?"
+ Символы.ПС + "Внимание: если не было кадровых изменений, записывать не рекомендуется!";
// Создаем описание оповещения, которое сработает после того, как пользователь нажмет кнопку в диалоге
ПараметрыОповещения = Новый Структура("ПараметрыЗаписи", ПараметрыЗаписи);
Оповещение = Новый ОписаниеОповещения("ПослеОтветаНаВопросЗаписать", ЭтотОбъект, ПараметрыОповещения);
// Показываем немодальный диалог
ПоказатьВопрос(Оповещение, ТекстВопроса, РежимДиалогаВопрос.ДаНет, , КодВозвратаДиалога.Нет, "Внимание!!!");
КонецПроцедуры
&НаКлиенте
Процедура ПослеОтветаНаВопросЗаписать(РезультатВопроса, ДополнительныеПараметры) Экспорт
// Если пользователь ответил утвердительно "Да"
Если РезультатВопроса = КодВозвратаДиалога.Да Тогда
// Восстанавливаем исходные параметры записи
ПараметрыЗаписи = ДополнительныеПараметры.ПараметрыЗаписи;
// Добавляем маркеры: один для обхода повторного вопроса, второй — для выполнения записи на сервере
ПараметрыЗаписи.Вставить("ПроверкаИнтерактивноВыполнена", Истина);
ПараметрыЗаписи.Вставить("ВыполнитьЗаписьВРегистр", Истина);
// Повторно вызываем запись объекта формы
ЭтотОбъект.Записать(ПараметрыЗаписи);
КонецЕсли;
КонецПроцедуры
---
Выясним причину, почему исходный код автора был опасен. Метод НаборЗаписей.Записать(Истина) без установленных отборов полностью очищает всю таблицу регистра в базе данных перед записью новых элементов. Если у вас в регистре было 10 000 записей по разным организациям, после выполнения такого кода останется только одна — текущая.
Для безопасной модификации данных в регистре сведений существует два правильных подхода.
Если в регистре сведений под каждую комбинацию измерений и периода пишется ровно одна строка, наиболее простым и безопасным решением будет использование объекта РегистрСведенийМенеджерЗаписи. Он работает точечно и автоматически выполняет операцию «Создать новую запись или обновить существующую»:
Процедура ЗаписатьВРегБезопасно(ИзмерениеСотрудник, ИмяИзмерения, ИмяРесурса, ДатаДанных)
Если ЗначениеНеЗаполнено(ИзмерениеСотрудник) Тогда
Возврат;
КонецЕсли;
// Инициализируем точечный менеджер записи
МЗ = РегистрыСведений.Организация.СоздатьМенеджерЗаписи();
МЗ.Период = ДатаДанных;
МЗ.Измерение = ИмяИзмерения; // Установка ключевого поля (измерения)
// Заполняем ресурс динамически
МЗ[ИмяРесурса] = ИзмерениеСотрудник;
Попытка
МЗ.Записать(); // Автоматически перезапишет данные по этому ключу, не трогая другие записи
Исключение
// Обязательно логируем ошибку в Журнал Регистрации для последующего анализа администратором
ЗаписьЖурналаРегистрации("РегистрацияСотрудников.ОшибкаЗаписи",
УровеньЖурналаРегистрации.Ошибка, , , ПодробноеПредставлениеОшибки(ИнформацияОбОшибке()));
ВызватьИсключение; // Передаем ошибку дальше, чтобы отменить транзакцию записи справочника
КонецПопытки;
КонецПроцедуры
Если специфика логики требует работы именно с набором записей, вы обязаны жестко ограничить область его действия с помощью фильтров (отборов). В этом случае будут перезаписаны только те строки, которые соответствуют заданному условию:
Набор = РегистрыСведений.Организация.СоздатьНаборЗаписей();
// Установка жесткого отбора по ключевым полям обязательна!
Набор.Отбор.Период.Установить(ДатаДанных);
Набор.Отбор.Измерение.Установить(ИмяИзмерения);
НоваяСтрока = Набор.Добавить();
НоваяСтрока.Период = ДатаДанных;
НоваяСтрока.Измерение = ИмяИзмерения;
НоваяСтрока[ИмяРесурса] = ИзмерениеСотрудник;
// Метод Записать(Истина) очистит и перезапишет данные ТОЛЬКО в пределах установленного отбора
Набор.Записать(Истина);
---
Проанализируем архитектурный аспект: где именно должна происходить запись в регистр сведений? Писать данные в базу непосредственно из процедур управляемой формы — плохой тон разработки. Если в процессе записи элемента справочника возникнет ошибка (например, сработает ограничение прав доступа или проверка заполнения реквизитов), запись справочника откатится, а данные в регистр сведений уже будут записаны.
Правильным решением является выполнение записи регистра в единой транзакции базы данных в модуле объекта справочника. Для передачи параметров из формы в модуль объекта используются ДополнительныеСвойства.
Рассмотрим, как связать форму и модуль объекта по шагам.
В модуле формы справочника пропишем обработчик события ПередЗаписьюНаСервере, который перенесет наш флаг согласия пользователя из параметров формы во внутренние свойства записываемого объекта:
&НаСервере
Процедура ПередЗаписьюНаСервере(Отказ, ТекущийОбъект, ПараметрыЗаписи)
// Если на клиенте пользователь согласился записать сведения, передаем этот флаг в свойства объекта
Если ПараметрыЗаписи.Свойство("ВыполнитьЗаписьВРегистр") Тогда
ТекущийОбъект.ДополнительныеСвойства.Вставить("ВыполнитьЗаписьВРегистр", Истина);
КонецЕсли;
КонецПроцедуры
Откроем модуль объекта справочника Организации. Перенесем процедуру записи регистра туда и вызовем ее в предопределенном обработчике события ПриЗаписи, который гарантированно выполняется внутри системной транзакции:
Процедура ПриЗаписи(Отказ)
// Проверяем наличие флага, переданного из управляемой формы
Если ДополнительныеСвойства.Свойство("ВыполнитьЗаписьВРегистр") Тогда
// Все действия выполняются внутри одной транзакции.
// Если здесь произойдет сбой, запись самого справочника также будет отменена.
ЗаписатьВРегБезопасно(ГлБухгалтер, "ГлБухгалтер", "Значение", ДатаДанных);
ЗаписатьВРегБезопасно(Кассир, "Кассир", "Значение", "Значение", ДатаДанных);
ЗаписатьВРегБезопасно(МинимальнаяЗарплата, "МинимальнаяЗарплата", "ЗначениеЧисло", ДатаДанных);
ЗаписатьВРегБезопасно(МинТариф, "МинТариф", "ЗначениеЧисло", ДатаДанных);
ЗаписатьВРегБезопасно(Руководитель, "Руководитель", "Значение", ДатаДанных);
ЗаписатьВРегБезопасно(БухгалтерПоПродаже, "БухПродаж", "Значение", ДатаДанных);
ЗаписатьВРегБезопасно(МенеджерПоПродажам, "МенеджерПоПродажам", "Значение", ДатаДанных);
КонецЕсли;
КонецПроцедуры
Процедура ЗаписатьВРегБезопасно(Сотрудник, ИмяИзмерения, ИмяРесурса, ДатаДанных)
Если ЗначениеНеЗаполнено(Сотрудник) Тогда
Возврат;
КонецЕсли;
МЗ = РегистрыСведений.Организация.СоздатьМенеджерЗаписи();
МЗ.Период = ДатаДанных;
МЗ.Измерение = ИмяИзмерения;
МЗ[ИмяРесурса] = Сотрудник;
Попытка
МЗ.Записать();
Исключение
ЗаписьЖурналаРегистрации("РегистрацияСотрудников.Ошибка",
УровеньЖурналаРегистрации.Ошибка, , , ПодробноеПредставлениеОшибки(ИнформацияОбОшибке()));
ВызватьИсключение; // Вызываем исключение для отката всей транзакции
КонецПопытки;
КонецПроцедуры
---
Мы полностью переработали устаревший небезопасный код. Применив данные изменения, мы получили стабильное, производительное решение, которое:
СоздатьМенеджерЗаписи().