При разработке в 1С:Предприятии 8.3 часто возникает необходимость не только сформировать печатную форму, но и предоставить пользователю возможность ее модификации, а затем сохранить внесенные изменения для дальнейшего использования. В этом руководстве мы подробно рассмотрим, как правильно организовать клиент-серверное взаимодействие для открытия формы с печатным документом и как затем сохранить измененный табличный документ в базе данных, используя регистры сведений и объект ХранилищеЗначения.
Изначальная проблема, с которой мы часто сталкиваемся, заключается в попытке вызова клиентских функций, таких как ПолучитьФорму, из серверного контекста. Платформа 1С строго разделяет выполнение кода на клиентской и серверной сторонах. Функция ПолучитьФорму предназначена для создания и открытия форм на стороне клиента, и попытка ее вызова на сервере приводит к ошибке "Процедура или функция с указанным именем не определена".
Давайте проанализируем пример неверного кода, который демонстрирует эту проблему:
&НаКлиенте
Процедура ОбработкаКоманды(ПараметрКоманды, ПараметрыВыполненияКоманды)
ТабМакет = Новый ТабличныйДокумент;
Печать(ТабМакет, ПараметрКоманды);
КонецПроцедуры
&НаСервере
Процедура Печать(ТабМакет, СсылкаНаДокумент)
тз = РегистрыСведений.ИсторияИзмененийТабДока.СрезПоследних(СсылкаНаДокумент.Дата, Новый Структура("Договор", СсылкаНаДокумент));
Если не тз = Неопределено и тз.Количество > 0 Тогда
данные = тз.Получить(0).ХЗ.Получить;
Если ТипЗнч(данные) = Тип("Структура") Тогда
т = 0;
Если данные.Свойство("Договор", т) Тогда
ФормаПечДок = ПолучитьФорму("Документ.Договор.Форма.ФормаДляПечДок", Новый Структура("ПечДок, ИмяПечДок, СсылкаНаДок", т, "Договор", СсылкаНаДокумент)); // <-- Здесь возникает ошибка
ФормаПечДок.Открыть;
Возврат;
КонецЕсли;
КонецЕсли;
КонецЕсли;
КонецПроцедуры
Как мы видим, в процедуре Печать, которая выполняется на сервере (директива &НаСервере), предпринимается попытка вызвать функцию ПолучитьФорму. Это и является основной причиной возникновения ошибки.
Для корректной работы с формами в управляемом приложении 1С нам необходимо строго соблюдать принципы клиент-серверного взаимодействия. Функция ПолучитьФорму должна вызываться исключительно на клиенте. Если для формирования параметров открытия формы требуются данные с сервера, их необходимо сначала получить серверным вызовом, а затем передать на клиент.
Разберем по шагам, как это реализовать:
Мы преобразуем серверную процедуру Печать в функцию, которая будет выполнять всю необходимую логику по подготовке данных и возвращать их на клиент. Эта функция может быть помечена как &НаСервере или &НаСервереБезКонтекста. Рекомендуем использовать &НаСервереБезКонтекста, если контекст текущей формы или объекта не требуется, так как это более производительный вариант. В нашем случае, функция будет возвращать структуру данных, необходимую для инициализации формы.
&НаСервере
Функция Печать(ТабМакет, СсылкаНаДокумент)
// Здесь мы ищем ранее сохраненные данные печатной формы
тз = РегистрыСведений.ИсторияИзмененийТабДока.СрезПоследних(СсылкаНаДокумент.Дата, Новый Структура("Договор", СсылкаНаДокумент));
Если не тз = Неопределено и тз.Количество > 0 Тогда
данные = тз.Получить(0).ХЗ.Получить(); // Получаем ХранилищеЗначения
Если ТипЗнч(данные) = Тип("Структура") Тогда
// Если данные являются структурой, пытаемся извлечь нужный табличный документ
Перем ТабличныйДокументДляПечати;
Если данные.Свойство("Договор", ТабличныйДокументДляПечати) Тогда
// Возвращаем найденный табличный документ на клиент
Возврат ТабличныйДокументДляПечати;
КонецЕсли;
КонецЕсли;
КонецЕсли;
// Если ничего не найдено, или данные некорректны, возвращаем пустой ТабличныйДокумент
Возврат Новый ТабличныйДокумент();
КонецФункции
Обратите внимание, что функция Печать теперь возвращает либо существующий табличный документ (если он найден в регистре), либо новый пустой ТабличныйДокумент.
После того как серверная функция вернула необходимые данные, мы можем вызвать ПолучитьФорму на клиенте. Функция ПолучитьФорму принимает в качестве параметров имя формы и структуру с параметрами, которые будут переданы в форму при ее создании. Эти параметры будут доступны в процедуре ПриСозданииНаСервере открываемой формы.
&НаКлиенте
Процедура ОбработкаКоманды(ПараметрКоманды, ПараметрыВыполненияКоманды)
// 1. Формируем или получаем ТабличныйДокумент на сервере
ТабДокДляОтображения = Печать(Неопределено, ПараметрКоманды); // Передаем ТабМакет, но сейчас он не используется для формирования
// 2. Открываем форму на клиенте, передавая ТабличныйДокумент и другие параметры
ФормаПечДок = ПолучитьФорму("Документ.Договор.Форма.ФормаДляПечДок",
Новый Структура("ПечДок, ИмяПечДок, СсылкаНаДок", ТабДокДляОтображения, "Договор", ПараметрКоманды));
ФормаПечДок.Открыть();
КонецПроцедуры
Теперь клиентская процедура ОбработкаКоманды вызывает серверную функцию Печать для получения уже сохраненного или нового табличного документа, а затем открывает форму, передавая этот документ и другие необходимые параметры.
Важно помнить, что если серверная функция не зависит от контекста объекта, из которого она вызывается, и не обращается к его реквизитам, то ее можно объявить с директивой &НаСервереБезКонтекста. Это может улучшить производительность за счет уменьшения объема данных, передаваемых между клиентом и сервером.
После того как мы успешно открыли форму с табличным документом, перед нами встает задача сохранения всех изменений, которые пользователь мог внести в этот документ. Мы должны обеспечить, чтобы измененный ТабМакет (представленный объектом ТабличныйДокумент) был сохранен в базе данных, желательно привязанным к конкретному документу и с возможностью перезаписи.
ХранилищаЗначенияДля сохранения сложных типов данных, таких как ТабличныйДокумент, в базе данных 1С предоставляет объект ХранилищеЗначения. Этот объект позволяет сериализовать практически любые данные (картинки, табличные документы, двоичные данные) и сохранить их в реквизите объекта базы данных. Чаще всего ХранилищеЗначения используют в регистрах сведений или в реквизитах документов, когда нет необходимости в прямом доступе к содержимому через запросы.
Рассмотрим пошагово, как реализовать сохранение:
При создании формы на сервере мы должны получить переданные параметры и инициализировать ими реквизиты формы. Именно здесь мы "поймаем" ТабличныйДокумент, переданный из клиентской процедуры ОбработкаКоманды.
&НаСервере
Процедура ПриСозданииНаСервере(Отказ, СтандартнаяОбработка)
// Получаем параметры, переданные при открытии формы
ПечДок = Параметры.ПечДок; // Это наш ТабличныйДокумент
ИмяПечДок = Параметры.ИмяПечДок; // Например, "Договор"
СсылкаНаДок = Параметры.СсылкаНаДок; // Ссылка на документ-владелец
// Устанавливаем заголовок формы для информативности
Элементы.ПечДок.Заголовок = "Форма """ + ИмяПечДок + """ от " + СсылкаНаДок;
КонецПроцедуры
Здесь ПечДок (реквизит формы с типом ТабличныйДокумент) будет содержать данные, которые можно отобразить в элементе формы типа "Поле табличного документа" и позволить пользователю редактировать.
Основная логика сохранения будет находиться в серверной функции, которая будет получать измененный ТабличныйДокумент и сохранять его в регистре сведений. Мы будем использовать регистр сведений ИсторияИзмененийТабДока (предполагая, что он имеет измерения, такие как Договор и Период, и ресурс ХЗ типа ХранилищеЗначения).
&НаСервереБезКонтекста
Функция ЗаписатьНаСервере(ПечДок, ИмяПечДок, СсылкаНаДок, Перезаписывать = Ложь)
// Создаем набор записей регистра для работы с данными
Набор = РегистрыСведений.ИсторияИзмененийТабДока.СоздатьНаборЗаписей();
// Устанавливаем отбор по измерениям, чтобы найти существующие записи
Набор.Отбор.Договор.Значение = СсылкаНаДок;
Набор.Отбор.Договор.Использование = Истина;
Набор.Отбор.Период.Значение = СсылкаНаДок.Дата; // Или другое подходящее поле для привязки к документу
Набор.Отбор.Период.Использование = Истина;
// Читаем записи, соответствующие отбору
Набор.Прочитать();
СтруктураХранения = Неопределено;
Если Набор.Количество = 1 Тогда
// Если запись найдена, пытаемся получить хранимые данные
СтруктураХранения = Набор.Получить(0).ХЗ.Получить();
// Проверяем, что полученные данные действительно являются структурой
Если ТипЗнч(СтруктураХранения) <> Тип("Структура") Тогда
СтруктураХранения = Неопределено;
КонецЕсли;
КонецЕсли;
// Если структура не найдена или некорректна, создаем новую
Если СтруктураХранения = Неопределено Тогда
СтруктураХранения = Новый Структура();
КонецЕсли;
// Проверяем, существует ли уже печатная форма с таким именем в структуре
Перем СуществующийПД;
Если СтруктураХранения.Свойство(ИмяПечДок, СуществующийПД) Тогда
// Если это ТабличныйДокумент и не разрешена перезапись, возвращаем 0
Если ТипЗнч(СуществующийПД) = Тип("ТабличныйДокумент") и не Перезаписывать Тогда
Возврат 0; // Сообщаем, что форма уже есть
КонецЕсли;
КонецЕсли;
// Вставляем или обновляем табличный документ в структуре
СтруктураХранения.Вставить(ИмяПечДок, ПечДок);
Набор.Очистить(); // Очищаем набор, чтобы добавить новую или обновленную запись
Запись = Набор.Добавить();
Запись.Договор = СсылкаНаДок;
Запись.Период = СсылкаНаДок.Дата; // Соответствует измерению регистра
Запись.ХЗ = Новый ХранилищеЗначения(СтруктураХранения); // Сохраняем всю структуру в ХранилищеЗначения
// Записываем набор записей в регистр. Истина - режим записи Замещать
Набор.Записать(Истина);
Возврат 1; // Успешная запись
КонецФункции
Эта функция реализует логику получения, обновления и записи данных в регистр сведений. Она позволяет хранить несколько печатных форм для одного документа, идентифицируя их по ИмяПечДок. Параметр Перезаписывать дает возможность контролировать поведение при обнаружении уже существующей формы.
На клиенте мы создадим процедуру, которая будет вызывать серверную функцию ЗаписатьНаСервере и обрабатывать результат ее выполнения. Эта процедура также может включать диалог с пользователем в случае обнаружения уже существующей формы.
&НаКлиенте
Процедура Записать(Команда)
// Пробуем записать документ без перезаписи
Если ЗаписатьНаСервере(ПечДок, ИмяПечДок, СсылкаНаДок, Ложь) = 0 Тогда
// Если запись не удалась (форма с таким именем уже есть)
Если Вопрос("Уже имеется такая печатная форма документа. Хотите перезаписать?", РежимДиалогаВопрос.ДаНетОтмена) = КодВозвратаДиалога.Да Тогда
// Если пользователь согласен перезаписать, вызываем серверную функцию с флагом Перезаписывать = Истина
Если не ЗаписатьНаСервере(ПечДок, ИмяПечДок, СсылкаНаДок, Истина) = 1 Тогда
Предупреждение("Не удалось записать изменения.");
Возврат;
КонецЕсли;
Иначе
Возврат; // Пользователь отказался перезаписывать
КонецЕсли;
КонецЕсли;
// Можно добавить сообщение об успешной записи
ПоказатьОповещениеПользователя("Изменения успешно сохранены.");
КонецПроцедуры
Эта клиентская процедура обеспечивает интерактивность с пользователем и управляет процессом сохранения, предлагая перезаписать существующую форму при необходимости.
Мы ранее уже скорректировали процедуру ОбработкаКоманды так, чтобы она вызывала серверную функцию Печать и передавала ее результат (ТабличныйДокумент) в форму. Теперь, когда форма открыта, элемент формы "Поле табличного документа" должен быть связан с реквизитом формы ПечДок. После редактирования пользователем, этот реквизит будет содержать измененный табличный документ, который затем передается в ЗаписатьНаСервере.
Рассмотрим, как будет выглядеть доработанная клиентская процедура ОбработкаКоманды, учитывающая формирование нового ТабДока, если он не был найден в регистре:
&НаКлиенте
Процедура ОбработкаКоманды(ПараметрКоманды, ПараметрыВыполненияКоманды)
// Попытаемся получить ранее сохраненный ТабличныйДокумент с сервера
ТабДокДляОтображения = Печать(Неопределено, ПараметрКоманды);
// Если ТабДокДляОтображения пуст (не найден или не был передан корректно),
// вы можете сформировать его заново, например, используя макет документа.
// Пример (предполагает наличие серверной функции для формирования ТабДока):
// Если ТабДокДляОтображения.КоличествоСтраниц = 0 Тогда
// ТабДокДляОтображения = СформироватьНовыйТабличныйДокументНаСервере(ПараметрКоманды);
// КонецЕсли;
// Открываем форму на клиенте, передавая ТабличныйДокумент и другие параметры
ФормаПечДок = ПолучитьФорму("Документ.Договор.Форма.ФормаДляПечДок",
Новый Структура("ПечДок, ИмяПечДок, СсылкаНаДок", ТабДокДляОтображения, "Договор", ПараметрКоманды));
ФормаПечДок.Открыть();
КонецПроцедуры
На форме ФормаДляПечДок необходимо создать реквизит ПечДок с типом ТабличныйДокумент и связать его с элементом формы типа "Поле табличного документа". Это позволит пользователю просматривать и редактировать документ. При вызове процедуры Записать из формы, реквизит ПечДок, содержащий измененный документ, будет передан в серверную функцию сохранения.
Важные моменты при использовании ХранилищаЗначения:
ХранилищеЗначения подходит для хранения больших объемов данных, но следует учитывать, что при каждом обращении к реквизиту с ХранилищемЗначения данные передаются целиком на клиент (если это не реквизит формы, а реквизит объекта). Это может замедлять работу при очень больших объемах или частых обращениях.
ХранилищеЗначения не доступен напрямую в управляемой форме. Для работы с ним всегда требуются серверные вызовы (Получить() и Новый ХранилищеЗначения()).
ХранилищеЗначения сериализует объекты. Это означает, что при сохранении теряются ссылки на другие объекты базы данных; сохраняется только их представление. При восстановлении объекты создаются заново.
ХранилищеЗначения необходимо передавать сам объект (например, ТабличныйДокумент), а не ссылку на элемент формы, который его отображает.
Помимо прямого сохранения ТабличногоДокумента в регистре сведений, существуют и другие методы работы с печатными формами в 1С:
Внешние печатные формы (ВПР): Для модификации типовых печатных форм без изменения конфигурации можно использовать внешние печатные формы. Они регистрируются в базе и могут быть изменены пользователем или разработчиком. ВПР позволяют добавлять и убирать текстовые поля, изменять форматирование текста, добавлять картинки, а также дают возможность легко откатить изменения к стандартному макету.
Хранение файлов в томах: Если табличные документы становятся очень большими или их количество исчисляется тысячами, что может негативно сказаться на производительности основной базы данных, рассмотрите возможность хранения файлов в томах. Тома - это отдельные хранилища на диске, позволяющие вынести бинарные данные за пределы основной информационной базы. Это уменьшает объем базы, ускоряет выполнение запросов и улучшает общую производительность системы.
Сохранение Табличного документа в различные форматы файлов: ТабличныйДокумент в 1С обладает обширными возможностями по сохранению в различные форматы: HTML, Excel (*.xls, *.xlsx), текстовый файл (*.txt), PDF, Word (*.docx) и собственный формат 1С – табличный документ v7.7 (*.mxl). Сохранение в форматы вроде Excel или PDF удобно для передачи документов внешним пользователям, у которых не установлена 1С. Однако при сохранении в некоторые форматы, например, Word, могут возникать искажения оформления для сложных макетов.
Общие рекомендации платформы: При работе с объектами 1С, такими как документы, любой объект считывается целиком при обращении к нему (например, через ПолучитьОбъект() или при открытии формы), даже если не все его данные отображаются. Это стоит учитывать при проектировании систем, работающих с большими объектами. При программном сохранении табличного документа в файл, если возникают ошибки доступа, может помочь выполнение кода на клиенте (директива &НаКлиенте), так как клиентский код имеет доступ к файловой системе пользователя.
Таким образом, мы рассмотрели комплексное решение для сохранения измененного табличного документа в 1С, начиная от правильной организации клиент-серверного взаимодействия и заканчивая использованием ХранилищаЗначения в регистре сведений. Эти подходы позволяют создавать гибкие и функциональные решения для работы с печатными формами.