Авторегистрация внешних отчётов/обработок средствами БСП

Вы сделали клиенту красивую печатную форму, или доп.обработку, или заполнение таб.части; но надо ещё зарегистрировать её. Клиенту это не всегда объяснишь, дистанционно не всегда сделаешь. А дальше её ещё обновлять каждый раз… Предлагаю код, который исключительно штатным образом "сделает всё сам".

Идея родилась из общения с крайне малограмотными клиентами, для которых в БП 3.0 добраться до справочника «Дополнительные отчёты и обработки» было нереально. Удалённого администрирования добиться не удалось. И тогда я подумал — а пусть внешка сама себя регистрирует. Уж проделать стандартное «Файл» — «Открыть» нынче всякий сможет.

И вот, стало быть, надо просто из «ПриОткрытии» основной формы отчёта/обработки что-то вызывать. Ниже приведено, что именно. Единственно, важно, чтобы в форме были параметр «ОбъектыНазначения» (произвольный) — ну, он-то для многих случаев необходим согласно требованиям БСП; и придуманный мной параметр «НезависимоеОткрытие» (булев, ключевой), он позволяет отличить, как вызвана внешка — через «Файл» — «Открыть» или уже штатно самой конфой. А если хотите, придумайте другие признаки, по которым в ПриОткрытии надо дёргать «ОбновлениеИзФайлаПомещениеНаКлиенте».

Проверялось для всех 36 случаев настроек регистрации внешки, на БСП 2.2.3.36 и 2.2.4.43; пока без прибамбасов вроде автоперезаполнения «Назначения» или расписания вызова команд (где оно возможно); но это тоже планирую сделать.

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

#Область СобытияФормы

&НаСервере
Процедура ПриСозданииНаСервере(Отказ, СтандартнаяОбработка)
    Если
ТипЗнч(ЭтаФорма.Параметры.ОбъектыНазначения)<>Тип(«Массив») Тогда
       
ЭтаФорма.Параметры.НезависимоеОткрытие=Истина;
    КонецЕсли;
КонецПроцедуры

&НаКлиенте
Процедура ПриОткрытии(Отказ)
    Если
ЭтаФорма.Параметры.НезависимоеОткрытие Тогда
       
ОбновлениеИзФайлаПомещениеНаКлиенте();
    КонецЕсли;
КонецПроцедуры

#КонецОбласти

#Область ОбслуживаниеАвторегистрацииПриОткрытииФормы

// Внимание! Все сообщения выдаются только из расчёта на русский язык, без использования НСтр!

&НаСервере
Процедура ОбновлениеИзФайлаМеханикаНаСервере(ПараметрыРегистрации)
   
//=============================================================================
    // Определяемся с самим объектом

    // Подключение и получение имени, под которым объект будет подключаться
    Менеджер=?(ПараметрыРегистрации.ЭтоОтчет,ВнешниеОтчеты,ВнешниеОбработки);

    // исходим из того, что у нас только тонкий или веб-клиент, получаем имя подключённой ВПФ
   
ИмяРегистрируемогоОбъекта=СокрЛП(Менеджер.Подключить(ПараметрыРегистрации.АдресДанныхОбработки,,Истина)); // в безопасном

    // проверим, есть ли уже такой объект (по ИмяОбъекта и Вид); будем считать, что блок используется (т.к. только это даёт конфликт имён)
    тз=«ВЫБРАТЬ ПЕРВЫЕ 1
    |   ТаблицаСправочника.Ссылка КАК ВПФ
    |ИЗ
    |   Справочник.ДополнительныеОтчетыИОбработки КАК ТаблицаСправочника
    |ГДЕ
    |   ТаблицаСправочника.ИмяОбъекта = &УслИмяОбъекта
    |   И ТаблицаСправочника.Публикация = ЗНАЧЕНИЕ(Перечисление.ВариантыПубликацииДополнительныхОтчетовИОбработок.Используется)
    |   И ТаблицаСправочника.ПометкаУдаления = Ложь»
;
   
//
   
Если ПараметрыРегистрации.ЭтоОтчет Тогда
       
тз=тз+«
        |   И ТаблицаСправочника.Вид В (
        |   ЗНАЧЕНИЕ(Перечисление.ВидыДополнительныхОтчетовИОбработок.ДополнительныйОтчет),
        |   ЗНАЧЕНИЕ(Перечисление.ВидыДополнительныхОтчетовИОбработок.Отчет))»
;
    Иначе
       
тз=тз+«
        |   И НЕ (ТаблицаСправочника.Вид В (
        |   ЗНАЧЕНИЕ(Перечисление.ВидыДополнительныхОтчетовИОбработок.ДополнительныйОтчет),
        |   ЗНАЧЕНИЕ(Перечисление.ВидыДополнительныхОтчетовИОбработок.Отчет)))»
;
    КонецЕсли;
   
//
   
з=Новый Запрос(тз);
   
з.УстановитьПараметр(«УслИмяОбъекта»,ИмяРегистрируемогоОбъекта);
   
//
   
УстановитьПривилегированныйРежим(Истина);
   
трез=з.Выполнить().Выгрузить(ОбходРезультатаЗапроса.Прямой);
   
УстановитьПривилегированныйРежим(Ложь);
   
//
   
Если трез.Количество()=0 Тогда // создаём новый объект в корне
       
ОбъектСправочника=Справочники.ДополнительныеОтчетыИОбработки.СоздатьЭлемент();
       
// считаю правильным проставить сразу же
       
ОбъектСправочника.ИспользоватьДляФормыСписка=Истина;
       
ОбъектСправочника.ИспользоватьДляФормыОбъекта=Истина;
    Иначе
       
ОбъектСправочника=трез[0].ВПФ.ПолучитьОбъект();
    КонецЕсли;

    //=============================================================================
    // Обрабатываем объект справочника

    КомандыСохраненные=ОбъектСправочника.Команды.Выгрузить(); // запомним команды
    //
    // запомним состояние реквизита Публикация и выключим временно, чтобы в регистрации обработки не делался поиск (мы уже его сделали)
   
ПубликацияСохраненная=ОбъектСправочника.Публикация;
   
ОбъектСправочника.Публикация=Перечисления.ВариантыПубликацииДополнительныхОтчетовИОбработок.Отключена;

    // Разрешения используются для всех, кроме глобальных обработок и отчётов, т.е. кроме ВидДопОбработка и ВидДопОтчет

    // Назначения используются для всех, кроме глобальных обработок и отчётов, т.е. кроме ВидДопОбработка и ВидДопОтчет
    // Единожды указанные Назначения не изменяются, если сведения о них не были переданы (т.е. удалённое — остаётся).
    // чтобы они перечитались, надо так: ОбъектСправочника.Назначение.Очистить(); // причём именно ДО вызова ЗарегистрироватьОбработку

    // собственно выполним большинство штатных действий по считыванию рег.сведений и регистрации в системе
    РезультатРегистрации=ДополнительныеОтчетыИОбработки.ЗарегистрироватьОбработку(ОбъектСправочника,ПараметрыРегистрации);

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

    Если не ПараметрыРегистрации.Успех Тогда
        Если не
ПустаяСтрока(ПараметрыРегистрации.КраткоеПредставлениеОшибки) Тогда
           
Сообщить(«Ошибка регистрации внешнего блока: «+СокрЛП(ПараметрыРегистрации.КраткоеПредставлениеОшибки),СтатусСообщения.Важное);
        Иначе
           
// считаем, что «занявших» имя не существует, и неуспех произошёл по другой причине:
            // а) не хватило прав на подключения обработки, запускаемой в небезопасном режиме;
            // б) не удалось сменить вид (обработка/отчёт итд) для уже имеющегося эл-та спр-ка;
            // в) вид ВПФ, указанный в регистрационных данных, противоречит расширению файла.
           
Сообщить(«Общая недиагностированная ошибка регистрации внешнего блока!»,СтатусСообщения.ОченьВажное);
        КонецЕсли;
        Возврат;
    КонецЕсли;

    ОбъектСправочника.Публикация=ПубликацияСохраненная; // восстановим значение реквизита Публикация

    // занимаемся таб.частью Команды
    тз=«ВЫБРАТЬ
    |   ДанныеРегистра.ИдентификаторКоманды,
    |   ДанныеРегистра.Пользователь
    |ИЗ
    |   РегистрСведений.ПользовательскиеНастройкиДоступаКОбработкам КАК ДанныеРегистра
    |ГДЕ
    |   ДанныеРегистра.ДополнительныйОтчетИлиОбработка = &УслСсылка
    |   И ДанныеРегистра.Доступно = Истина»
;
   
з=Новый Запрос(тз);
   
з.УстановитьПараметр(«УслСсылка»,ОбъектСправочника.Ссылка);
   
УстановитьПривилегированныйРежим(Истина);
   
БыстрыйДоступ=з.Выполнить().Выгрузить(ОбходРезультатаЗапроса.Прямой);
   
УстановитьПривилегированныйРежим(Ложь);

    рАдресРазрешений=ПоместитьВоВременноеХранилище(ОбъектСправочника.Разрешения.Выгрузить(),ЭтотОбъект.УникальныйИдентификатор);

    тКоманд=ОбъектСправочника.Команды.Выгрузить();
   
тКоманд.Сортировать(«Представление»);

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

    // в таблице Команды нужны колонки, которых нет как реквизитов табчасти Команды, добавим
   
тКоманд.Колонки.Добавить(«РегламентноеЗаданиеИспользование»,Новый ОписаниеТипов(«Булево»));
   
тКоманд.Колонки.Добавить(«РегламентноеЗаданиеПредставление»); // строка0
   
тКоманд.Колонки.Добавить(«РегламентноеЗаданиеРазрешено»,Новый ОписаниеТипов(«Булево»));
   
тКоманд.Колонки.Добавить(«РегламентноеЗаданиеРасписание»,Новый ОписаниеТипов(«СписокЗначений»)); // обычно 1 элемент типа «РасписаниеРегламентногоЗадания»
   
тКоманд.Колонки.Добавить(«БыстрыйДоступПредставление»); // строка0

    Для каждого строКоманд Из тКоманд Цикл
       
строКоманд.РегламентноеЗаданиеИспользование=Ложь; // по умолчанию
       
строКоманд.РегламентноеЗаданиеРазрешено=Ложь; // по умолчанию
        //
       
Если ВидОбъекта=ВидДополнительнаяОбработка или ВидОбъекта=ВидДополнительныйОтчет    Тогда
           
// сделаем представление строки в зависимости от количества пользователей, найденных по этой команде в регистре доступа
           
мНайденных=БыстрыйДоступ.НайтиСтроки(Новый Структура(«ИдентификаторКоманды»,строКоманд.Идентификатор));
            Если
мНайденных.Количество()=0 Тогда
               
строКоманд.БыстрыйДоступПредставление=«Нет»;
            Иначе
// схалявим — незачем красивости разводить
               
строКоманд.БыстрыйДоступПредставление=«Пользователей: «+СокрЛП(мНайденных.Количество());
            КонецЕсли;
        КонецЕсли;
       
//
       
Если ВидОбъекта=ВидДополнительнаяОбработка
        И (строКоманд.ВариантЗапуска=Перечисления.СпособыВызоваДополнительныхОбработок.ВызовСерверногоМетода
        ИЛИ строКоманд.ВариантЗапуска=Перечисления.СпособыВызоваДополнительныхОбработок.СценарийВБезопасномРежиме)
        Тогда
           
строКоманд.РегламентноеЗаданиеРазрешено=Истина;
           
//
           
РегламентноеЗаданиеGUID=строКоманд.РегламентноеЗаданиеGUID;
           
НайденнаяСтрока=КомандыСохраненные.Найти(строКоманд.Идентификатор,«Идентификатор»);
            Если
НайденнаяСтрока<>Неопределено Тогда
               
РегламентноеЗаданиеGUID=НайденнаяСтрока.РегламентноеЗаданиеGUID;
            КонецЕсли;
           
//
           
Если ЗначениеЗаполнено(РегламентноеЗаданиеGUID) Тогда // ID задания есть, разбираемся с самим заданием
               
РегламентноеЗадание=ДополнительныеОтчетыИОбработкиРегламентныеЗадания.НайтиЗадание(РегламентноеЗаданиеGUID);
                Если
РегламентноеЗадание<>Неопределено Тогда
                   
ПараметрыЗадания=ДополнительныеОтчетыИОбработкиРегламентныеЗадания.ПолучитьПараметрыЗадания(РегламентноеЗадание);
                   
// ставим параметры задания в строку команд
                   
строКоманд.РегламентноеЗаданиеGUID=РегламентноеЗаданиеGUID;
                   
строКоманд.РегламентноеЗаданиеПредставление=Строка(ПараметрыЗадания.Расписание);
                   
строКоманд.РегламентноеЗаданиеИспользование=ПараметрыЗадания.Использование;
                   
строКоманд.РегламентноеЗаданиеРасписание.Вставить(0,ПараметрыЗадания.Расписание);
                   
// выключаем, если надо
                   
Если строКоманд.РегламентноеЗаданиеПредставление=ПредставлениеПустогоРасписания Тогда
                       
строКоманд.РегламентноеЗаданиеИспользование=Ложь;
                    КонецЕсли;
                КонецЕсли;
            КонецЕсли;
           
//
           
Если Не строКоманд.РегламентноеЗаданиеИспользование Тогда
               
строКоманд.РегламентноеЗаданиеПредставление=«Расписание не задано»;
            КонецЕсли;
        Иначе
           
// если это не глобальная доп.обработка
           
строКоманд.РегламентноеЗаданиеПредставление=«Неприменимо для команд с вариантом запуска «»»+СокрЛП(строКоманд.ВариантЗапуска)+«»»!»;
        КонецЕсли;
    КонецЦикла;
// по таблице команд

    ОбъектСправочника.Команды.Загрузить(тКоманд);

    // обратно включим использование
   
ОбъектСправочника.Публикация=Перечисления.ВариантыПубликацииДополнительныхОтчетовИОбработок.Используется;

    Если ДополнительныеОтчетыИОбработки.ВозможнаЗагрузкаОбработкиИзФайла(ОбъектСправочника.Ссылка) Тогда
       
ддОбработки=ПолучитьИзВременногоХранилища(ПараметрыРегистрации.АдресДанныхОбработки);
       
ОбъектСправочника.ХранилищеОбработки=Новый ХранилищеЗначения(ддОбработки,Новый СжатиеДанных(9));
    КонецЕсли;

    Если ВидОбъекта=ВидДополнительнаяОбработка или ВидОбъекта=ВидДополнительныйОтчет Тогда
       
ОбъектСправочника.ДополнительныеСвойства.Вставить(«АктуальныеКоманды»,тКоманд);
    Иначе
       
БыстрыйДоступ.Очистить();
    КонецЕсли;
   
ОбъектСправочника.ДополнительныеСвойства.Вставить(«БыстрыйДоступ»,БыстрыйДоступ);

    ОбъектСправочника.Разрешения.Загрузить(ПолучитьИзВременногоХранилища(рАдресРазрешений));
   
ОбъектСправочника.Ответственный=ПараметрыСеанса.ТекущийПользователь;

    // собственно запишем
   
Попытка
       
ОбъектСправочника.Записать();
       
ПараметрыРегистрации.Вставить(«СсылкаНаОбъект»,ОбъектСправочника.Ссылка);
    Исключение
       
Сообщить(«Ошибка финальной записи регистрации: «+ОписаниеОшибки(),СтатусСообщения.ОченьВажное);
       
ПараметрыРегистрации.Вставить(«Успех»,Ложь);
    КонецПопытки;

КонецПроцедуры

&НаСервере
Функция ОпределитьИмяИспользуемогоФайла()
   
рОбъект=РеквизитФормыВЗначение(«Объект»);
    Возврат
рОбъект.ИспользуемоеИмяФайла;
КонецФункции

&НаКлиенте
Процедура ОбновлениеИзФайлаПомещениеНаКлиенте()
   
рПутьИмяФайла=ОпределитьИмяИспользуемогоФайла();

    // помещаем файл в хранилище на сервере
   
рДопПараметры=Новый Структура(«Успешность»,Истина); // предполагаем, что так
   
рОбработчик=Новый ОписаниеОповещения(«ПомещениеФайлаНаСерверЗавершение»,ЭтотОбъект,рДопПараметры);
   
//
   
Если ПодключитьРасширениеРаботыСФайлами() Тогда
       
мПомещенныхФайлов=Новый Массив;
        Если не
ПоместитьФайлы(,мПомещенныхФайлов,рПутьИмяФайла,Ложь,ЭтаФорма.УникальныйИдентификатор) Тогда
           
Сообщить(«Ошибка при помещении файла внешнего отчёта/обработки на сервер!»,СтатусСообщения.Важное);
           
рДопПараметры.Вставить(«Успешность»,Ложь);
        Иначе
           
// мПомещенныхФайлов содержит элемент — служебку вида Имя (путь) и Хранение (GUID)
           
рДопПараметры.Вставить(«Успешность»,Истина);
        КонецЕсли;
       
ВыполнитьОбработкуОповещения(рОбработчик,мПомещенныхФайлов);
    Иначе
       
НачатьПомещениеФайла(рОбработчик,,рПутьИмяФайла,Ложь,ЭтаФорма.УникальныйИдентификатор);
    КонецЕсли;
КонецПроцедуры

&НаКлиенте
Процедура ПомещениеФайлаНаСерверЗавершение(мПомещенныхФайлов,рПараметрыРегистрации) Экспорт
   
рОписаниеФайла=мПомещенныхФайлов.Получить(0);
   
//
   
мстро=СтроковыеФункцииКлиентСервер.РазложитьСтрокуВМассивПодстрок(рОписаниеФайла.Имя,«»);
   
рПараметрыРегистрации.Вставить(«ИмяФайла»,мстро.Получить(мстро.ВГраница()));
   
рПараметрыРегистрации.Вставить(«АдресДанныхОбработки»,рОписаниеФайла.Хранение);
   
//
    // выясним тип, исходя из расширения (если бы не веб-клиент, можно было бы через объект «Файл»)
   
рРасширение=ВРег(Прав(рПараметрыРегистрации.ИмяФайла,3));
    Если
рРасширение=«ERF» Тогда
       
рПараметрыРегистрации.Вставить(«ЭтоОтчет»,Истина);
    ИначеЕсли
рРасширение=«EPF» Тогда
       
рПараметрыРегистрации.Вставить(«ЭтоОтчет»,Ложь);
    Иначе
       
Сообщить(«Расширение файла не соответствует расширению внешнего отчёта (ERF) или обработки (EPF)!»,СтатусСообщения.Важное);
       
рПараметрыРегистрации.Вставить(«Успешность»,Ложь);
        Возврат;
    КонецЕсли;
   
//
   
рПараметрыРегистрации.Вставить(«ОтключатьПубликацию»,Ложь);
   
рПараметрыРегистрации.Вставить(«ОтключатьКонфликтующие»,Ложь);
   
рПараметрыРегистрации.Вставить(«Конфликтующие»,Новый СписокЗначений);

    // Подготовка к вызову сервера.
    //ОбработчикРезультата = ПараметрыРегистрации.ОбработчикРезультата;
    //ПараметрыРегистрации.Удалить(«ОбработчикРезультата»);

    //============================================================================
    // Вызов сервера.
    ОбновлениеИзФайлаМеханикаНаСервере(рПараметрыРегистрации);

    //============================================================================

    Если рПараметрыРегистрации.Успех Тогда
        Если
рПараметрыРегистрации.Свойство(«СсылкаНаОбъект») и ЗначениеЗаполнено(рПараметрыРегистрации.СсылкаНаОбъект) Тогда
           
гипСсылка=ПолучитьНавигационнуюСсылку(рПараметрыРегистрации.СсылкаНаОбъект);
           
ПоказатьОповещениеПользователя(«Авторегистрация выполнена»,гипСсылка,«Для перехода к регистрационной карточке щёлкните по ссылке.»,гипСсылка);
           
ЭтаФорма.Закрыть();
           
//парф=Новый Структура(«Ключ»,рПараметрыРегистрации.СсылкаНаОбъект);
            //ОткрытьФорму(«Справочник.ДополнительныеОтчетыИОбработки.Форма.ФормаЭлемента»,парф);
       
Иначе
           
ПоказатьОповещениеПользователя(«При завершении авторегистрации внешнего блока произошла ошибка!»);
        КонецЕсли;
    Иначе
       
ПоказатьОповещениеПользователя(«Авторегистрация внешнего блока не удалась!»);
    КонецЕсли;

КонецПроцедуры

#КонецОбласти

 

Кто найдёт ошибки — пожалуйста, сообщайте, буду исправлять. Вообще, заранее оговорюсь, что развитие БСП в любой момент может сделать любую из вышеприведённых строк неактуальной, но, по идее, капитальных изменений идеологии быть уже не должно.

P.S. Если будет время, попробую разобрать каждый вариант подключения и использования внешки в отдельной статье, т.к. там есть весьма любопытные и нетривиальные заморочки.

13 Comments

  1. Поручик

    Всё давно придумано http://infostart.ru/public/181707/ , а сама идея идёт со времён расцвета УТ 10.3 http://infostart.ru/public/75166/

    Reply
  2. DoctorRoza

    М-да!! Люди БСП юзают, тонкие клиенты всякие .. а я сижу в древней УПП 1.2, толстый клиент .. гоняю! 🙁

    Reply
  3. Yashazz

    (1), Поручик, спасибо. Конечно, были подозрения, что идея — баян) В данном случае делюсь с коллегами исключительно реализацией под нынешние БСП, никак не «ноу-хау»))) Пардон, сбаянил)

    (2) Так и я по основной работе сижу на режиме совместимости 8.1 в очень толстом клиенте… Это так, результат небольших левых работ.

    Reply
  4. DAnry

    М-да!! два раза! А в старой доброй 8.2 с обычным НЕ управляемым интерфейсом всё намного проще…

    Reply
  5. vandalsvq

    Почему используешь «Сообщить» вместо «СообщениеПользователю» (или БСП-шной процедуры ОбщегоНазначенияКлиентСервер.СообщитьПользователю)?

    Понимаю не критично, но все-таки. Из-за возможности использовать статус сообщения?

    Цитата «Методические рекомендации 1С»

    При использовании в конфигурации Библиотеки стандартных подсистем рекомендуется использовать процедуру СообщитьПользователю общего модуля ОбщегоНазначенияКлиентСервер, которая работает с объектом СообщениеПользователю.

    Reply
  6. Yashazz

    (5) vandalsvq, да просто привычка. В БСП-то эта функция есть, а я уж привык делать универсалы. Только и всего.

    Reply
  7. mbreaker

    (2) DoctorRoza, прямо так и напрашивается дополнить фразой «И бед не знаю, в отличие от вас!» :)))))

    Reply
  8. Yashazz

    По здравому размышлению советую добавить

    и не Параметры.Свойство(«ИдентификаторКоманды»)

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

    Reply
  9. DoctorRoza

    (7) mbreaker, не соглашусь! Нет! Конечно, намного меньше проблем и заморочек с решением задач. Но, новые технологии нужно изучать, тем более 8.3.6 на подходе. 😐

    Reply
  10. Swetlana

    (9) DoctorRoza, да ктож вам мешает то? Вместо вас никто этого делать не будет 😉

    Ну или … идите во франч, они загрузят по самое …любят они ставить изощрённые задачи. )))

    Reply
  11. jobkostya1c8

    Ну если уже

    из общения с крайне малограмотными клиентами, для которых в БП 3.0 добраться до справочника «Дополнительные отчёты и обработки» было нереально

    то точно с такими клиентами беда.

    Reply
  12. Patriot1S

    (0) Для очень ленивых предлагаю http://infostart.ru/public/343316/

    Reply
  13. Поручик

    (11) Я все свои внешние формы с авторегистрацией делаю. Не потому, что лень или малограмотный, а для ускорения отладки. Ну и для удобства. Открыл и ВПФ зарегистрировалась сама.

    Reply

Leave a Comment

Ваш адрес email не будет опубликован. Обязательные поля помечены *