При автоматизации процессов, переносе данных или создании обработок в среде «1С:Предприятие 8.3» разработчики часто сталкиваются с архитектурным препятствием. Нам необходимо программно создать, заполнить и рассчитать документ (например, «Отпуск», «Больничный лист» или «Начисление зарплаты» в конфигурации «1С:Зарплата и управление персоналом 3.1»). Однако при попытке вызвать процедуру расчета выясняется, что вся ключевая логика находится внутри модуля управляемой формы документа.
Попытка вызвать метод ПолучитьФорму() на сервере предсказуемо завершается ошибкой «Интерактивные операции недоступны». Прямой вызов клиентских экспортных процедур формы из фоновых заданий или серверного контекста невозможен. В этой статье мы подробно разберем, как решить эту проблему, рассмотрим несколько эффективных подходов — от простых обходных путей до правильных архитектурных паттернов.
В идеальной архитектуре бизнес-логика должна быть полностью отделена от интерфейса (паттерн MVC). Расчетные механизмы должны располагаться в модуле объекта, менеджера или в общих модулях. Но на практике, особенно в сложных типовых решениях уровня ERP и ЗУП, разработчики часто завязывают расчеты на контекст формы. Процедуры расчета требуют передачи самой формы в качестве параметра для обновления экранных элементов, динамических списков или чтения временных данных, хранящихся в реквизитах формы.
Давайте разберем по шагам, какими способами мы можем обойти это ограничение и выполнить расчет документа программно.
---
Если мы работаем в конфигурации «1С:Зарплата и управление персоналом 3.1», наиболее правильным решением является использование штатного программного механизма — Менеджера расчета зарплаты. Этот инструмент позволяет полностью отказаться от эмуляции форм и выполнить расчет на сервере напрямую.
Проанализируем типичный сценарий использования этого механизма. Вместо вызова процедур формы мы передаем данные в менеджер расчета, инициализируем его и получаем на выходе готовые рассчитанные таблицы.
Рассмотрим пример программного заполнения и расчета начисления:
// 1. Создаем и заполняем базовые реквизиты документа
НовыйДокумент = Документы.Отпуск.СоздатьДокумент();
НовыйДокумент.Дата = ТекущаяДата();
НовыйДокумент.Организация = Справочники.Организации.НайтиПоРеквизиту("ИНН", "7701000000");
НовыйДокумент.Сотрудник = Справочники.Сотрудники.НайтиПоКоду("00001");
НовыйДокумент.ПериодРегистрации = НачалоМесяца(ТекущаяДата());
НовыйДокумент.ДатаНачала = Дата("20230801");
НовыйДокумент.ДатаОкончания = Дата("20230814");
// 2. Инициализируем менеджер расчета для нашей организации и периода
МесяцНачисления = НовыйДокумент.ПериодРегистрации;
МенеджерРасчета = РасчетЗарплатыРасширенный.СоздатьМенеджерРасчета(МесяцНачисления, НовыйДокумент.Организация);
// 3. Настраиваем параметры расчета
МенеджерРасчета.НастройкиРасчета.РассчитыватьНачисления = Истина;
МенеджерРасчета.НастройкиРасчета.РассчитыватьНДФЛ = Истина;
// 4. Подготавливаем исходные данные
// В реальной задаче таблицы заполняются на основе данных документа или переносимых данных
ТаблицаСотрудников = МенеджерРасчета.ТаблицаИсходныеДанныеНачисленияЗарплатыПоНачислениям();
НоваяСтрока = ТаблицаСотрудников.Добавить();
НоваяСтрока.Сотрудник = НовыйДокумент.Sотрудник;
НоваяСтрока.ДатаНачала = НовыйДокумент.ДатаНачала;
НоваяСтрока.ДатаОкончания = НовыйДокумент.ДатаОкончания;
// 5. Запускаем расчет на сервере
МенеджерРасчета.РассчитатьЗарплату();
// 6. Переносим рассчитанные показатели в документ
Для Каждого СтрокаРезультата Из МенеджерРасчета.ТаблицыРезультатов.Начисления Цикл
ЗаполнитьЗначенияСвойств(НовыйДокумент.Начисления.Добавить(), СтрокаРезультата);
КонецЦикла;
НовыйДокумент.Записать(РежимЗаписиДокумента.Проведение);
Преимущества: Полностью серверный код, высокая скорость работы, отсутствие привязки к интерфейсу, высокая надежность при обновлении конфигурации.
---
Что делать, если логика расчета в типовой конфигурации слишком сильно завязана на форму (например, обращается к реквизитам через ЭтотОбъект или использует специфические свойства формы), а переписывать весь алгоритм нет времени? В этом случае мы можем применить паттерн проектирования «Класс-обманка».
Суть метода заключается в том, что мы создаем структуру, которая по своему составу свойств полностью дублирует реквизиты формы. Мы передаем эту структуру в расчетные процедуры вместо реальной формы.
Выполним это по шагам:
Функция СоздатьКонтекстФормы(ДокументОбъект) Экспорт
ИмитацияФормы = Новый Структура;
// Имитируем основной реквизит формы "Объект"
ПараметрыОбъекта = Новый Структура;
ПараметрыОбъекта.Вставить("Ссылка", ДокументОбъект.Ссылка);
ПараметрыОбъекта.Вставить("Организация", ДокументОбъект.Организация);
ПараметрыОбъекта.Вставить("Сотрудник", ДокументОбъект.Сотрудник);
// Переносим табличные части в виде таблиц значений
ПараметрыОбъекта.Вставить("Начисления", ДокументОбъект.Начисления.Выгрузить());
ПараметрыОбъекта.Вставить("РасчетСреднего", ДокументОбъект.РасчетСреднего.Выгрузить());
ИмитацияФормы.Вставить("Объект", ПараметрыОбъекта);
// Имитируем другие свойства формы, к которым может обращаться код расчета
ИмитацияФормы.Вставить("Параметры", Новый Структура);
ИмитацияФормы.Вставить("Элементы", Новый Структура);
Возврат ИмитацияФормы;
КонецФункции
ИмитацияФормы:
// Создаем документ в режиме сервера
ДокОбъект = Документы.Отпуск.СоздатьДокумент();
// ... заполняем реквизиты ...
КонтекстФормы = СоздатьКонтекстФормы(ДокОбъект);
// Вызываем типовой серверный метод расчета, который обычно вызывается из формы.
// Передаем наш эмулированный контекст вместо реальной формы.
ОбработкаТабличныхЧастейПривод.РассчитатьДокументНаСервере(КонтекстФормы);
// После выполнения расчета переносим измененные данные обратно в документ
ДокОбъект.Начисления.Загрузить(КонтекстФормы.Объект.Начисления);
ДокОбъект.Записать(РежимЗаписиДокумента.Запись);
Преимущества: Позволяет выполнять сложные расчеты без изменения типового кода, "обманывая" расчетные процедуры за счет идентичности структуры данных.
---
Фирма «1С» постепенно переносит логику из форм в модули объектов. Перед тем как изобретать сложные обходные пути, обязательно проверьте, нет ли в модуле объекта нужного вам документа готовых экспортных методов для расчета.
В современных релизах ЗУП для многих документов реализована поддержка программного расчета. Схема работы выглядит следующим образом:
ДокОбъект = Документы.Отпуск.СоздатьДокумент();
// Заполняем ключевые реквизиты
ДокОбъект.Организация = ОрганизацияСсылка;
ДокОбъект.Сотрудник = СотрудникСсылка;
ДокОбъект.ДатаНачала = ДатаНачала;
ДокОбъект.ДатаОкончания = ДатаОкончания;
// Проверяем наличие встроенных методов расчета в модуле объекта
Попытка
// Метод подготавливает внутренние структуры документа к расчету
ДокОбъект.ДокументГотовКРасчету(Истина);
// Запускаем расчет начислений и среднего заработка
ПараметрыРасчета = Новый Структура;
ДокОбъект.Рассчитать(ПараметрыРасчета);
ДокОбъект.Записать(РежимЗаписиДокумента.Проведение);
Исключение
ЗаписьЖурналаРегистрации("ОшибкаРасчета", УровеньЖурналаРегистрации.Ошибка, , , ПодробноеПредставлениеОшибки(ИнформацияОбОшибке()));
КонецПопытки;
Преимущества: Самый чистый и надежный способ. Если разработчики конфигурации предусмотрели данный метод, используйте исключительно его.
---
Если ваша обработка создания и заполнения документов запускается интерактивно пользователем на клиенте, мы можем программно открыть форму документа в скрытом режиме, передать туда данные, выполнить расчет штатными средствами формы и забрать результат.
Проанализируем этот алгоритм:
КопироватьДанныеФормы.Пример реализации на клиенте:
&НаКлиенте
Процедура СоздатьИРассчитатьДокументНаКлиенте()
// 1. Получаем форму нового документа
ПараметрыФормы = Новый Структура;
ФормаДокумента = ПолучитьФорму("Документ.Отпуск.Форма.ФормаДокумента", ПараметрыФормы);
// 2. Создаем объект на сервере и заполняем его
НовыйДокументОбъект = СоздатьИЗаполнитьДокументНаСервере();
// 3. Копируем данные объекта в форму
КопироватьДанныеФормы(НовыйДокументОбъект, ФормаДокумента.Объект);
// 4. Вызываем процедуру расчета, которая объявлена в форме как экспортная
ФормаДокумента.РассчитатьДокументНаКлиенте();
// 5. Забираем рассчитанные данные обратно в наш прикладной объект
КопироватьДанныеФормы(ФормаДокумента.Объект, НовыйДокументОбъект);
// 6. Записываем готовый документ на сервере
ЗаписатьДокументНаСервере(НовыйДокументОбъект);
КонецПроцедуры
Преимущества: Позволяет задействовать абсолютно все интерфейсные проверки, обработчики событий и интерактивные расчеты без необходимости их дублирования в коде.
---
Подведем итоги нашего анализа и выберем оптимальный путь решения задачи:
МенеджерРасчетаЗарплаты (Решение 1). Это убережет ваш код от сбоев при последующих обновлениях системы.КопироватьДанныеФормы (Решение 4), чтобы сохранить все интерактивные проверки системы.