Как перепровести документы по отбору при запуске системы 1С:Предприятие 8?

Программист 1С v8.3 (Управляемые формы) 1С:Управление торговлей Управленческий учет Торговля и дистрибуция
← На главную

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

В чем заключается проблема и почему возникает ошибка?

Предположим, у нас есть задача перепровести документы "Счет на оплату покупателю", которые имеют некий дополнительный реквизит Расш_Резервировать, установленный в Истина. Мы хотим, чтобы при каждом запуске системы проверялись эти счета, и если условия резервирования изменились (например, истекла дата резерва), документ перепроводился, обнуляя резерв и оповещая пользователя.

Наш первый инстинкт — поместить код в процедуру ПриНачалеРаботыСистемы в Модуле управляемого приложения, как это работало в старых версиях или на обычных формах:


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

При попытке выполнить этот код в тонком клиенте (или веб-клиенте) мы столкнемся с ошибками, такими как:

  1. "Переменная не определена (Документы)" при обращении к Документы.СчетНаОплатуПокупателю.

  2. "Ругается на переменную Запрос" при попытке создать Новый Запрос().

Причина этих ошибок кроется в архитектуре управляемого приложения и тонкого клиента. Тонкий клиент предназначен для минимизации нагрузки на клиентскую машину и канал связи. Он не имеет прямого доступа к базе данных и, соответственно, к прикладным объектам, которые работают с данными (таким как менеджеры документов Документы.СчетНаОплатуПокупателю или объект Запрос). Все операции с базой данных и прикладными объектами должны выполняться на стороне сервера.

Процедуры в Модуле управляемого приложения, включая ПриНачалеРаботыСистемы, по умолчанию выполняются на клиенте. Именно поэтому мы не можем использовать серверные объекты напрямую в этом контексте.

Решение 1: Использование Модуля Сеанса

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

Как работает Модуль Сеанса?

Модуль Сеанса содержит обработчик события УстановкаПараметровСеанса. Эта процедура является самым первым программно доступным обработчиком событий при старте системы. Здесь мы имеем полный доступ к серверным объектам, включая менеджеры документов, справочников и объект Запрос.

Пример реализации в Модуле Сеанса:

  1. Откройте конфигуратор.

  2. В дереве конфигурации найдите раздел "Общие".

  3. Разверните его и найдите "Модуль сеанса". Откройте его двойным кликом.

  4. В процедуре УстановкаПараметровСеанса разместите наш код:


// В Модуле Сеанса
Процедура УстановкаПараметровСеанса(Параметры)
    // Эта процедура выполняется на сервере при каждом старте сеанса.
    // Здесь мы имеем доступ к серверным объектам.

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

Важные моменты при работе с Модулем Сеанса:

  1. Отсутствие пользовательского интерфейса: В Модуле Сеанса мы не можем взаимодействовать с пользователем напрямую через функции типа Сообщить() или выводить диалоги, так как пользовательский интерфейс еще не инициализирован. Для информирования о результатах или ошибках следует использовать ЗаписьЖурналаРегистрации().

  2. Производительность: Если документов для перепроведения много, это может существенно замедлить процесс запуска системы для каждого пользователя. Рекомендуется оптимизировать запрос и логику перепроведения, чтобы минимизировать нагрузку.

  3. Блокировки: При массовом перепроведении документов могут возникнуть блокировки данных, особенно в многопользовательском режиме. Продумайте логику обработки ошибок и повторных попыток.

  4. Файловая база: Для файловой базы Модуль Сеанса отлично подходит, так как регламентные задания в чистом виде там не работают. Каждое открытие базы любым пользователем будет запускать этот код.

Решение 2: Вызов серверной процедуры из Модуля Управляемого Приложения

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

Как это работает?

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

Шаги реализации:

  1. Создание Общего модуля:

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

    • "Клиент (обычное приложение)" и "Клиент (управляемое приложение)": Если вы планируете вызывать функции из этого модуля на клиенте.

    • "Сервер": Обязательно, чтобы серверные функции могли быть определены в этом модуле.

    • "Вызов сервера": КРИТИЧЕСКИ ВАЖНО! Этот флаг позволяет вызывать экспортные функции этого модуля с клиента на сервер. Без него вы получите ошибку "Процедура или функция с указанным именем не определена".

  2. Определение серверной процедуры в Общем модуле:

    В созданном Общем модуле определим экспортную серверную процедуру, которая будет выполнять всю логику перепроведения:

    
    // В ОбщемМодуле (например, 'МодульРаботыСДокументамиСервер')
    // Свойства модуля: 'Сервер', 'Вызов сервера' (обязательно!), 'Клиент (управляемое приложение)'
    
    &НаСервере
    Процедура ПровестиСчетаПоОтборуНаСервере() Экспорт
        // Переменные Документы и Запрос здесь доступны, так как код выполняется на сервере.
        СтОтбор = Новый Структура;
        СтОтбор.Вставить("Расш_Резервировать", Истина);
        
        ВыборкаДокументов = Документы.СчетНаОплатуПокупателю.Выбрать(, , СтОтбор);
        
        СписокСообщенийДляПользователя = Новый СписокЗначений; // Для сбора сообщений
        
        Пока ВыборкаДокументов.Следующий() Цикл
            Попытка
                ДокументОбъект = ВыборкаДокументов.ПолучитьОбъект();
                
                Если ДокументОбъект.Расш_Резервировать
                   И ДокументОбъект.Расш_ДатаОкончанияРезерва < ТекущаяДата() Тогда
                    
                    ДокументОбъект.Записать(РежимЗаписиДокумента.Проведение);
                    СписокСообщенийДляПользователя.Добавить(
                        "Документ " + ДокументОбъект.Номер + " от " + ДокументОбъект.Дата
                        + " автоматически перепроведен (резерв обнулен)."
                    );
                КонецЕсли;
            Исключение
                СписокСообщенийДляПользователя.Добавить(
                    "Ошибка при перепроведении документа " + ВыборкаДокументов.Ссылка + ": " + ОписаниеОшибки()
                );
                ЗаписьЖурналаРегистрации(
                    "Ошибка автоматического перепроведения",
                    УровеньВажностиЖурналаСобытий.Ошибка,
                    Метаданные.Документы.СчетНаОплатуПокупателю,
                    ВыборкаДокументов.Ссылка,
                    "Ошибка при перепроведении документа " + ВыборкаДокументов.Ссылка + ": " + ОписаниеОшибки()
                );
            КонецПопытки;
        КонецЦикла;
        
        // Передаем список сообщений обратно на клиент, если нужно
        Если СписокСообщенийДляПользователя.Количество() > 0 Тогда
            ПараметрыСеанса.СообщенияПриЗапуске = СписокСообщенийДляПользователя;
        КонецЕсли;
    КонецПроцедуры
    

    В этом примере мы собираем сообщения о перепроведении в список и сохраняем его в параметрах сеанса. Это позволит нам прочитать эти сообщения на клиенте.

  3. Вызов серверной процедуры из Модуля Управляемого Приложения:

    Теперь мы можем вызвать эту серверную процедуру из ПриНачалеРаботыСистемы:

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

При таком подходе ошибка "Процедура или функция с указанным именем не определена" для ПровестиСчетаНаСервере (из сообщения 6) часто возникает именно из-за того, что общий модуль, содержащий эту процедуру, не имеет установленного флага "Вызов сервера" или сама процедура не является Экспорт.

Альтернативный способ реализации серверного кода (используя запрос):

Вместо Документы.СчетНаОплатуПокупателю.Выбрать() можно использовать объект Запрос, что может быть более производительно для больших объемов данных.


// Пример использования Запроса в серверной процедуре
&НаСервере
Процедура ПровестиСчетаПоОтборуЧерезЗапросНаСервере() Экспорт
    Запрос = Новый Запрос;
    Запрос.Текст = 
        "ВЫБРАТЬ
        |    СчетНаОплатуПокупателю.Ссылка КАК Ссылка
        |ИЗ
        |    Документ.СчетНаОплатуПокупателю КАК СчетНаОплатуПокупателю
        |ГДЕ
        |    СчетНаОплатуПокупателю.Расш_Резервировать = ИСТИНА
        |    И СчетНаОплатуПокупателю.Расш_ДатаОкончанияРезерва < &ТекущаяДата";
    
    Запрос.УстановитьПараметр("ТекущаяДата", ТекущаяДата());
    
    РезультатЗапроса = Запрос.Выполнить();
    ВыборкаДетальныеЗаписи = РезультатЗапроса.Выбрать();
    
    СписокСообщенийДляПользователя = Новый СписокЗначений;

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

Общие рекомендации и предостережения

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

  2. Тестирование: Всегда тестируйте новый функционал на копии рабочей базы или на тестовом контуре, чтобы убедиться в корректности работы и отсутствии негативного влияния на производительность и другие процессы.

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

  4. Блокировки данных: В многопользовательском режиме массовое перепроведение может привести к конфликтам и блокировкам данных, особенно если другие пользователи работают с теми же документами. Продумайте стратегию обработки таких ситуаций (например, использование блокировок на уровне транзакций, повторные попытки или отложенное перепроведение).

  5. Альтернативные методы:

    • Типовая обработка "Групповое перепроведение документов": Если массовое перепроведение требуется не ежедневно, а периодически, можно использовать стандартную обработку, которая обычно находится в разделах "Администрирование - Обслуживание - Корректировка данных" или "Операции - Сервис".

    • Регламентные задания: Для клиент-серверного варианта базы данных регламентные задания являются идеальным решением для фонового выполнения таких операций, так как они не замедляют запуск клиентских приложений. Однако, для файловых баз регламентные задания напрямую не применимы. В файловой базе можно использовать внешние планировщики задач (например, "Планировщик заданий Windows") для запуска внешних обработок или пакетных файлов, которые программно запускают 1С в режиме предприятия или конфигуратора с выполнением нужного кода.

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

← На главную