Как сохранить измененный табличный документ (ТабМакет) в документе 1С?

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

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

Проблема: Вызов клиентских функций на сервере

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

Давайте проанализируем пример неверного кода, который демонстрирует эту проблему:


&НаКлиенте
Процедура ОбработкаКоманды(ПараметрКоманды, ПараметрыВыполненияКоманды)
    ТабМакет = Новый ТабличныйДокумент;
    Печать(ТабМакет, ПараметрКоманды);
КонецПроцедуры

&НаСервере
Процедура Печать(ТабМакет, СсылкаНаДокумент)
    тз = РегистрыСведений.ИсторияИзмененийТабДока.СрезПоследних(СсылкаНаДокумент.Дата, Новый Структура("Договор", СсылкаНаДокумент));
    Если не тз = Неопределено и тз.Количество > 0 Тогда
        данные = тз.Получить(0).ХЗ.Получить;
        Если ТипЗнч(данные) = Тип("Структура") Тогда
            т = 0;
            Если данные.Свойство("Договор", т) Тогда
                ФормаПечДок = ПолучитьФорму("Документ.Договор.Форма.ФормаДляПечДок", Новый Структура("ПечДок, ИмяПечДок, СсылкаНаДок", т, "Договор", СсылкаНаДокумент)); // <-- Здесь возникает ошибка
                ФормаПечДок.Открыть;
                Возврат;
            КонецЕсли;
        КонецЕсли;
    КонецЕсли;
КонецПроцедуры

Как мы видим, в процедуре Печать, которая выполняется на сервере (директива &НаСервере), предпринимается попытка вызвать функцию ПолучитьФорму. Это и является основной причиной возникновения ошибки.

Решение 1: Правильное взаимодействие клиента и сервера при открытии формы

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

Разберем по шагам, как это реализовать:

  1. Шаг 1: Получение данных с сервера для формы

    Мы преобразуем серверную процедуру Печать в функцию, которая будет выполнять всю необходимую логику по подготовке данных и возвращать их на клиент. Эта функция может быть помечена как &НаСервере или &НаСервереБезКонтекста. Рекомендуем использовать &НаСервереБезКонтекста, если контекст текущей формы или объекта не требуется, так как это более производительный вариант. В нашем случае, функция будет возвращать структуру данных, необходимую для инициализации формы.

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

    Обратите внимание, что функция Печать теперь возвращает либо существующий табличный документ (если он найден в регистре), либо новый пустой ТабличныйДокумент.

  2. Шаг 2: Открытие формы на клиенте с переданными параметрами

    После того как серверная функция вернула необходимые данные, мы можем вызвать ПолучитьФорму на клиенте. Функция ПолучитьФорму принимает в качестве параметров имя формы и структуру с параметрами, которые будут переданы в форму при ее создании. Эти параметры будут доступны в процедуре ПриСозданииНаСервере открываемой формы.

    
    &НаКлиенте
    Процедура ОбработкаКоманды(ПараметрКоманды, ПараметрыВыполненияКоманды)
        // 1. Формируем или получаем ТабличныйДокумент на сервере
        ТабДокДляОтображения = Печать(Неопределено, ПараметрКоманды); // Передаем ТабМакет, но сейчас он не используется для формирования
    
        // 2. Открываем форму на клиенте, передавая ТабличныйДокумент и другие параметры
        ФормаПечДок = ПолучитьФорму("Документ.Договор.Форма.ФормаДляПечДок", 
                                    Новый Структура("ПечДок, ИмяПечДок, СсылкаНаДок", ТабДокДляОтображения, "Договор", ПараметрКоманды));
        ФормаПечДок.Открыть();
    КонецПроцедуры
    

    Теперь клиентская процедура ОбработкаКоманды вызывает серверную функцию Печать для получения уже сохраненного или нового табличного документа, а затем открывает форму, передавая этот документ и другие необходимые параметры.

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

Проблема 2: Сохранение измененного табличного документа

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

Решение 2: Хранение табличного документа в регистре сведений с помощью ХранилищаЗначения

Для сохранения сложных типов данных, таких как ТабличныйДокумент, в базе данных 1С предоставляет объект ХранилищеЗначения. Этот объект позволяет сериализовать практически любые данные (картинки, табличные документы, двоичные данные) и сохранить их в реквизите объекта базы данных. Чаще всего ХранилищеЗначения используют в регистрах сведений или в реквизитах документов, когда нет необходимости в прямом доступе к содержимому через запросы.

Рассмотрим пошагово, как реализовать сохранение:

  1. Шаг 1: Инициализация формы для отображения и передачи данных

    При создании формы на сервере мы должны получить переданные параметры и инициализировать ими реквизиты формы. Именно здесь мы "поймаем" ТабличныйДокумент, переданный из клиентской процедуры ОбработкаКоманды.

    
    &НаСервере
    Процедура ПриСозданииНаСервере(Отказ, СтандартнаяОбработка)
        // Получаем параметры, переданные при открытии формы
        ПечДок = Параметры.ПечДок; // Это наш ТабличныйДокумент
        ИмяПечДок = Параметры.ИмяПечДок; // Например, "Договор"
        СсылкаНаДок = Параметры.СсылкаНаДок; // Ссылка на документ-владелец
    
        // Устанавливаем заголовок формы для информативности
        Элементы.ПечДок.Заголовок = "Форма """ + ИмяПечДок + """ от " + СсылкаНаДок;
    КонецПроцедуры
    

    Здесь ПечДок (реквизит формы с типом ТабличныйДокумент) будет содержать данные, которые можно отобразить в элементе формы типа "Поле табличного документа" и позволить пользователю редактировать.

  2. Шаг 2: Серверная функция для записи табличного документа

    Основная логика сохранения будет находиться в серверной функции, которая будет получать измененный ТабличныйДокумент и сохранять его в регистре сведений. Мы будем использовать регистр сведений ИсторияИзмененийТабДока (предполагая, что он имеет измерения, такие как Договор и Период, и ресурс ХЗ типа ХранилищеЗначения).

    
    &НаСервереБезКонтекста
    Функция ЗаписатьНаСервере(ПечДок, ИмяПечДок, СсылкаНаДок, Перезаписывать = Ложь)
    
        // Создаем набор записей регистра для работы с данными
        Набор = РегистрыСведений.ИсторияИзмененийТабДока.СоздатьНаборЗаписей();
    
        // Устанавливаем отбор по измерениям, чтобы найти существующие записи
        Набор.Отбор.Договор.Значение = СсылкаНаДок;
        Набор.Отбор.Договор.Использование = Истина;
        Набор.Отбор.Период.Значение = СсылкаНаДок.Дата; // Или другое подходящее поле для привязки к документу
        Набор.Отбор.Период.Использование = Истина;
    
        // Читаем записи, соответствующие отбору
        Набор.Прочитать();
    
        СтруктураХранения = Неопределено;
        Если Набор.Количество = 1 Тогда
            // Если запись найдена, пытаемся получить хранимые данные
            СтруктураХранения = Набор.Получить(0).ХЗ.Получить();
            // Проверяем, что полученные данные действительно являются структурой
            Если ТипЗнч(СтруктураХранения) <> Тип("Структура") Тогда
                СтруктураХранения = Неопределено;
            КонецЕсли;
        КонецЕсли;
    
        // Если структура не найдена или некорректна, создаем новую
        Если СтруктураХранения = Неопределено Тогда
            СтруктураХранения = Новый Структура();
        КонецЕсли;
    
        // Проверяем, существует ли уже печатная форма с таким именем в структуре
        Перем СуществующийПД;
        Если СтруктураХранения.Свойство(ИмяПечДок, СуществующийПД) Тогда
            // Если это ТабличныйДокумент и не разрешена перезапись, возвращаем 0
            Если ТипЗнч(СуществующийПД) = Тип("ТабличныйДокумент") и не Перезаписывать Тогда
                Возврат 0; // Сообщаем, что форма уже есть
            КонецЕсли;
        КонецЕсли;
    
        // Вставляем или обновляем табличный документ в структуре
        СтруктураХранения.Вставить(ИмяПечДок, ПечДок);
    
        Набор.Очистить(); // Очищаем набор, чтобы добавить новую или обновленную запись
        Запись = Набор.Добавить();
        Запись.Договор = СсылкаНаДок;
        Запись.Период = СсылкаНаДок.Дата; // Соответствует измерению регистра
        Запись.ХЗ = Новый ХранилищеЗначения(СтруктураХранения); // Сохраняем всю структуру в ХранилищеЗначения
    
        // Записываем набор записей в регистр. Истина - режим записи Замещать
        Набор.Записать(Истина);
    
        Возврат 1; // Успешная запись
    КонецФункции
    

    Эта функция реализует логику получения, обновления и записи данных в регистр сведений. Она позволяет хранить несколько печатных форм для одного документа, идентифицируя их по ИмяПечДок. Параметр Перезаписывать дает возможность контролировать поведение при обнаружении уже существующей формы.

  3. Шаг 3: Клиентская процедура для вызова сохранения

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

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

    Эта клиентская процедура обеспечивает интерактивность с пользователем и управляет процессом сохранения, предлагая перезаписать существующую форму при необходимости.

  4. Шаг 4: Передача сформированного ТабличногоДокумента

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

    Рассмотрим, как будет выглядеть доработанная клиентская процедура ОбработкаКоманды, учитывающая формирование нового ТабДока, если он не был найден в регистре:

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

    На форме ФормаДляПечДок необходимо создать реквизит ПечДок с типом ТабличныйДокумент и связать его с элементом формы типа "Поле табличного документа". Это позволит пользователю просматривать и редактировать документ. При вызове процедуры Записать из формы, реквизит ПечДок, содержащий измененный документ, будет передан в серверную функцию сохранения.

Важные моменты при использовании ХранилищаЗначения:

  1. Объемы данных: ХранилищеЗначения подходит для хранения больших объемов данных, но следует учитывать, что при каждом обращении к реквизиту с ХранилищемЗначения данные передаются целиком на клиент (если это не реквизит формы, а реквизит объекта). Это может замедлять работу при очень больших объемах или частых обращениях.
  2. Доступность на форме: Реквизит объекта с типом ХранилищеЗначения не доступен напрямую в управляемой форме. Для работы с ним всегда требуются серверные вызовы (Получить() и Новый ХранилищеЗначения()).
  3. Сериализация: ХранилищеЗначения сериализует объекты. Это означает, что при сохранении теряются ссылки на другие объекты базы данных; сохраняется только их представление. При восстановлении объекты создаются заново.
  4. Передача объектов: При записи в ХранилищеЗначения необходимо передавать сам объект (например, ТабличныйДокумент), а не ссылку на элемент формы, который его отображает.

Альтернативные подходы и рекомендации

Помимо прямого сохранения ТабличногоДокумента в регистре сведений, существуют и другие методы работы с печатными формами в 1С:

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

Хранение файлов в томах: Если табличные документы становятся очень большими или их количество исчисляется тысячами, что может негативно сказаться на производительности основной базы данных, рассмотрите возможность хранения файлов в томах. Тома - это отдельные хранилища на диске, позволяющие вынести бинарные данные за пределы основной информационной базы. Это уменьшает объем базы, ускоряет выполнение запросов и улучшает общую производительность системы.

Сохранение Табличного документа в различные форматы файлов: ТабличныйДокумент в 1С обладает обширными возможностями по сохранению в различные форматы: HTML, Excel (*.xls, *.xlsx), текстовый файл (*.txt), PDF, Word (*.docx) и собственный формат 1С – табличный документ v7.7 (*.mxl). Сохранение в форматы вроде Excel или PDF удобно для передачи документов внешним пользователям, у которых не установлена 1С. Однако при сохранении в некоторые форматы, например, Word, могут возникать искажения оформления для сложных макетов.

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

Таким образом, мы рассмотрели комплексное решение для сохранения измененного табличного документа в 1С, начиная от правильной организации клиент-серверного взаимодействия и заканчивая использованием ХранилищаЗначения в регистре сведений. Эти подходы позволяют создавать гибкие и функциональные решения для работы с печатными формами.

← На главную