Как я обработку на альтернативный сервер выносил

Публикация № 998389

Разработка - Практика программирования

экспорт импорт обработка com C# 1c альтернативный сервер тяжелая обработка

В данном посте хочу поделиться опытом. Однажды возник инцидент, который смотивировал реализовать обработку, которую запускал бы обычный пользователь 1С, в основной системе. Но весь процесс обработки должен происходить за пределами рабочей базы. А юзабилити должно остаться на уровне простого пользователя. В качестве решения я выбрал службу Windows (С#), приложение инициации на клиенте и далее прошу под кат...

Всем доброго времени суток. Не буду сейчас вдаваться во все подробности, ибо это займет много Вашего и моего времени. 

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

Суть обработки проста. Выполняется сложный запрос с массой соединений и расчетов. Результирующая таблица пишется в ряд регистров сведений для последующего извлечения конечными отчетами. Своего рода OLAB куб внутри 1С. Пишется это все в ряд регистров, во имя нормализации и оптимизации дискового пространства, занимаемого базой. 

Как происходит запуск? Ежедекадно, специалисты департамента актуарных расчетов, получив отмашку от департамента статистики, как правило это происходит ближе к 19:00 вечера, запускают обработочку, где указывают дату сведений, на которую необходимо сформировать базу действующих договоров и связанные с ней расчеты, указывают флажками отчеты которые необходимо получить по завершении (отчеты выгружаются в Excel, в заранее определенную директорию), и нажимают кнопку "Сформировать". Все. Далее специалисты уходят домой, а 1С продолжает работать приблизительно до полуночи. Но это не суть.

Как же сделать что бы пользователь не ощутил серьезной разницы и какого-то подвоха, но при этом что бы все обработки данных произошли на альтернативном сервере, а данные при этом появились в рабочей базе данных?

Решение оказалось весьма простым. 

На альтернативном сервере я установил службу Windows, которая прослушивает IP и Port. Как только по указанному порту на этот IP адрес, поступает сообщение с определенным кодовым словом, запускается процесс. В данном процессе я прописал все то, что до этого я какое-то время делал руками. А именно - программа снимает свежий бэкап рабочей базы. Данный бэкап помещается в расшаренную директорию рабочего сервера. Затем из этой директории файл перемещается на альтернативный сервер. Свежий бэкап восстанавливается на специальную базу для подготовки отчетности. А далее через COM соединение, на альтернативном сервере, в специальной базе данных запускается та самая обработка, с теми самыми параметрами, которые до этого указал пользователь. По завершению обработки, данные, средствами SQL bcp (bulk copy program) переносятся на рабочую базу данных. А тем временем, на альтернативном сервере запускается регламентное задание по заданному расписанию, и начинает выгружать необходимые отчеты. Таким образом, все тяжелые запросы и обработки данных выносятся за пределы рабочего сервера, что существенно снижает нагрузку. А так же предотвращает ошибки связанные с блокировками. Единственная серьезная нагрузка в этом алгоритме происходит в момент коммита данных переносимых с помощью sql bcp. 

 

А теперь попробую привести некоторые наглядные фрагменты этого всего процесса. 

Весь процесс у нас начинается с приложения-инициатора. Там все просто. 

Создаем в VS консольное приложение на C#. В тексте основного модуля пишем такой код:

using System;
using System.Net.Sockets;
using System.Text;


namespace ExtFS_Client_Consol
{
    class Program
    {

        /// <summary>
        /// Отправляем на сервер запрос на инициацию процесса формирования БДН
        /// </summary>
        private static string SendMess(string IP, int Port)
        {

            //Инициализация
            string SMess = @"StartFormation";
            char[] outMessage = SMess.ToCharArray();

            try
            {
                TcpClient client = new TcpClient(IP, Port); 
                Byte[] data = Encoding.UTF8.GetBytes(outMessage);
                NetworkStream stream = client.GetStream();

                try
                {
                    stream.Write(data, 0, data.Length);

                    Byte[] comingdata = new Byte[256];
                    stream = client.GetStream();

                    string responseData = String.Empty;
                    StringBuilder completeMessage = new StringBuilder();
                    int numberOfBytesRead = 0;
                    do
                    {
                        numberOfBytesRead = stream.Read(comingdata, 0, comingdata.Length);
                        completeMessage.AppendFormat("{0}", Encoding.UTF8.GetString(comingdata, 0, numberOfBytesRead));
                    }
                    while (stream.DataAvailable);
                    responseData = completeMessage.ToString();
                    return responseData;
                }
                finally
                {
                    stream.Close();
                    client.Close();
                }
            }
            catch (Exception err)
            {
                Console.WriteLine(err.Message);
                return @"";
            }
        }

        static void Main(string[] args)
        {
            if (args.Length > 0)
            {
                try
                {
                    string IP = args[0];
                    int Port = Convert.ToInt16(args[1]);
                    string response = SendMess(IP, Port);
                }
                catch (Exception err)
                {
                    Console.WriteLine(err.Message);
                }
            }
            else
            {
                Console.WriteLine(@"Не переданы параметры запуска! Выполнение - не возможно!");
                Console.ReadKey();
            }
        }
    }
}

Как понятно из текста, значение IP адреса и номер порта мы передаем через параметры запуска. Если такие параметры не заданы при запуске, то и результат работы будет нулевой. Выйдет лишь уведомление с предупреждением о том что ничего не запущено. 

Далее, создаем в нашей системе 1С, константу с типом "ХранилищеЗначений". Она нам понадобиться для хранения скомпилированного консольного приложения. Я не буду тут описывать как именно этот exe попадет в константу. А вот что мы делаем когда пользователь нажимает кнопку "Сформировать" смотрите далее.

Во первых необходимо провести подготовительные работы перед отправкой сообщения на альтернативный сервер. А именно поместить в базу данные о том, на какую дату формируем данные, какие отчеты и с какими параметрами будем выгружать после. И все что Вам взблагорассудится. Исходя конечно же из Вашей задачи. А далее запускаем инициацию запуска. Моя процедура выглядит примерно вот так:

// Метод инициализации запуска формирования БДН/ЯО на альтернативном сервере. 
// Происходит сохранение во временную директорию exe файла, и запуск его с 
// параметрами (ip, port). EXE файл в свою очередь отправляет через tcp/ip 
// команду старта на сервер.
Процедура ИнициироватьЗапускФормированияНаАльтернативномСервере() Экспорт
	
	Попытка
		ПФ = ПолучитьПараметрыВнешнегоФормирования();
		ВремКаталог = КаталогВременныхФайлов();
		ФайловыйПакет = Константы.ИсполняемыйФайлИнициацииВнешнегоФормированияЯО.Получить().Получить();
		ДвоичныеДанные = ФайловыйПакет.Файл.Получить();
		ПутьФайла = ВремКаталог + ФайловыйПакет.ИмяФайла;
		НашФайл = Новый Файл(ПутьФайла);
		Если НашФайл.Существует() Тогда
			УдалитьФайлы(ПутьФайла);
			ДвоичныеДанные.Записать(ПутьФайла);
		Иначе
			ДвоичныеДанные.Записать(ПутьФайла);
		КонецЕсли;
		WSHShell = Новый COMОбъект("WScript.Shell");
		WSHShell.CurrentDirectory = ВремКаталог;
		WSHShell.Run(ФайловыйПакет.ИмяФайла + " " + ПФ.IP + " " + ПФ.Port, 0);
	Исключение
		#Если Клиент Тогда
			Сообщить("Ошибка при попытки запустить программу инициализации удаленного формирования БДН\ЯО: " + ОписаниеОшибки());
		#КонецЕсли
	КонецПопытки;
	
КонецПроцедуры

Сразу после запуска данного метода подключаем обработчик ожидания такого плана:

		ПодключитьОбработчикОжидания("ПроверкаСтатусаЗапускаВнешнегоФормирования", 4);

Сам подключаемый метод:  

// Обработчик каждые 2 секунды проверяет соответствующий регистр сведений, поменялся ли там статус на Истина.
// Если после 7й попытки статус не изменился - выводится сообщение о возможной ошибке при инициализации. 
// Если статус поменялся - выводится сообщение об успешном запуске внешнего формирования.
// В любом случае после заверешения обработчика ожидания, запись в регистре сведений - удаляется. 
Процедура ПроверкаСтатусаЗапускаВнешнегоФормирования()
	Запрос = Новый Запрос;
	Запрос.Текст = "ВЫБРАТЬ
	               |	УдаленноеФормированиеБДН.ДатаСведений КАК ДатаСведений
	               |ИЗ
	               |	РегистрСведений.УдаленноеФормированиеБДН КАК УдаленноеФормированиеБДН
	               |ГДЕ
	               |	УдаленноеФормированиеБДН.ДатаСведений = &ДатаСведений
	               |	И УдаленноеФормированиеБДН.ПроцессЗапущен";
	Запрос.УстановитьПараметр("ДатаСведений", ДатаСведений);
	Если Запрос.Выполнить().Выгрузить().Количество() > 0 Тогда
		Сообщить("Внешнее формирование успешно запущено!");
		ОтключитьОбработчикОжидания("ПроверкаСтатусаЗапускаВнешнегоФормирования");
		Набор = РегистрыСведений.УдаленноеФормированиеБДН.СоздатьНаборЗаписей();
		Набор.Очистить();
		Набор.Записать();
	Иначе
		КолПроверокСтатусаВФЯО = КолПроверокСтатусаВФЯО + 1;
		Если КолПроверокСтатусаВФЯО = 15 Тогда
			Сообщить("Внешнее формирование не было запущено на стороне альтернативного сервера, либо возникли проблемы с обратной связью. Необходимо проверить процесс выполнения.", СтатусСообщения.Важное);
			ОтключитьОбработчикОжидания("ПроверкаСтатусаЗапускаВнешнегоФормирования");
			Набор = РегистрыСведений.УдаленноеФормированиеБДН.СоздатьНаборЗаписей();
			Набор.Очистить();
			Набор.Записать();
		КонецЕсли;
	КонецЕсли;
КонецПроцедуры

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

Так как же устроена серверная часть данной системы, которая по сути своей, является службой Windows? Давайте разбираться по порядку. 

Создаем проект Службы Windows. Добавляем новый класс. Назовем его по смыслу. В моем случае это удаленный сервис формирования, поэтому я назвал его RFS_Serv. Содержимое класса привожу ниже.

using System;
using System.ServiceProcess;
using System.IO;
using System.Threading;

namespace RFS_Service
{
    public partial class RFS_Serv : ServiceBase
    {
        private static IPListener MyListener = null;
        private static Thread MyThread;
        private LogWriter Logger;
        private Props XMLSett;

        public RFS_Serv()
        {
            InitializeComponent();
        }

        protected override void OnStart(string[] args)
        {
            try
            {
                Logger = new LogWriter(Path.GetDirectoryName(System.Reflection.Assembly.GetExecutingAssembly().Location));

                XMLSett = new Props();
                XMLSett.ReadXml();

                MyListener = new IPListener(Path.GetDirectoryName(System.Reflection.Assembly.GetExecutingAssembly().Location), XMLSett.Fields.LocalIP, XMLSett.Fields.Port);

                MyThread = new Thread(new ThreadStart(MyListener.StartListener));
                MyThread.Name = "ListenerThread";
                MyThread.Start();
                Logger.Log(@"Служба запущена");
            }
            catch (Exception err)
            {
                string mess = new ExceptionMessages().GetExMess(err);
                Logger.Log(@"error: " + mess);
            }
        }

        protected override void OnStop()
        {
            try
            {
                MyListener.NeedListen = false;
                Logger.Log(@"Служба остановлена");

                MyListener.Dispose();
                Logger.Dispose();
                GC.SuppressFinalize(XMLSett);
                GC.SuppressFinalize(MyThread);
            }
            catch (Exception err)
            {
                string mess = new ExceptionMessages().GetExMess(err);
                Logger.Log(@"error: " + mess);
            }
        }
    }
}

Данный класс является ключевым классом сервиса, и в обработчике события OnStart мы инициируем запуск "прослушки" ip адреса. Для этого мы описываем класс IPListener. Его описание так же привожу ниже:

using System;
using System.Text;
using System.Net;
using System.Net.Sockets;

namespace RFS_Service
{
    class IPListener
    {

        /// <summary>
        /// Путь от куда была запущена программа
        /// </summary>
        private static string _ProgrammLocation = @"";
        public string ProgrammLocation { get { return _ProgrammLocation; } set { _ProgrammLocation = value; } }

        /// <summary>
        /// IP адресс по которому ведем прослушивание
        /// </summary>
        private static string _curIPAdress = @"";
        public string curIPAdress { get { return _curIPAdress; } set { _curIPAdress = value; } }

        /// <summary>
        /// Port по котором ведем прослушивание
        /// </summary>
        private static int _curPort = 951;
        public int curPort { get { return _curPort; } set { _curPort = value; } }

        private static bool _NeedListen = false;
        public bool NeedListen { get { return _NeedListen; } set { _NeedListen = value; } }

        LogWriter Logger;

        /// <summary>
        /// Конструктор экземпляра класса
        /// </summary>
        /// <param name="ApplicationPath">Директория из которой запущено приложение</param>
        public IPListener(string ApplicationPath, string IP, int Port)
        {
            Logger = new LogWriter(ApplicationPath);
            ProgrammLocation = ApplicationPath;
            curIPAdress = IP;
            curPort = Port;
            NeedListen = false; //по умолчанию ничего не слушаем
        }

        /// <summary>
        /// Деструктор экземпляра класса
        /// </summary>
        public void Dispose()
        {
            Logger.Dispose();
            GC.SuppressFinalize(this);
        }

        public void StartListener()
        {

            _NeedListen = true;

            IPAddress localAddr = IPAddress.Parse(_curIPAdress);
            int port = _curPort;
            TcpListener server = new TcpListener(localAddr, port);
            string CommandFromClient = @"";
            char[] TrimChars = { ' ', '0', '\0' };
            server.Start();

            while (_NeedListen)
            {
                try
                {
                    TcpClient client = server.AcceptTcpClient();
                    NetworkStream stream = client.GetStream();
                    try
                    {
                        if (stream.CanRead)
                        {
                            byte[] myReadBuffer = new byte[1024];
                            StringBuilder myCompleteMessage = new StringBuilder();
                            int numberOfBytesRead = 0;
                            do
                            {
                                numberOfBytesRead = stream.Read(myReadBuffer, 0, myReadBuffer.Length);
                                myCompleteMessage.AppendFormat("{0}", Encoding.UTF8.GetString(myReadBuffer, 0, myReadBuffer.Length));

                            }
                            while (stream.DataAvailable);

                            CommandFromClient = myCompleteMessage.ToString().Trim(TrimChars);
                            if (CommandFromClient == @"StartFormation")
                            {
                                Logger.Log(@"Принята команда запуска формирования.");
                                RFS rfs = new RFS(); //Запустим процесс формирования ЯО БДН на текущем сервере. 
                                rfs.Execute();
                                rfs.Dispose();
                            }
                            else
                            {
                                Byte[] responseData = Encoding.UTF8.GetBytes(DateTime.Now.ToString(@"Полученный запрос не имеет описания на сервере!"));
                                stream.Write(responseData, 0, responseData.Length);
                                Logger.Log(@"Принята не опознанная команда!");
                            }
                        }

                    }
                    finally
                    {
                        stream.Close();
                        client.Close();
                    }
                }
                catch
                {
                    Logger.Log(@"Ошибка во время прослушивания");
                    break;
                }
            }

        }

        public void StopListen()
        {
            _NeedListen = false;
        }

    }
}

Как видим из содержимого, суть класса проста. При определенных условиях запускается цикл в отдельном потоке, в котором происходит прослушка IP И Port. Приходящие сообщения анализируются, и если пришла определенная команда, например "StartFormation", программа инициализирует класс RFS и запускает процесс обработки на удаленном (альтернативном) сервере. Класс RFS в моем случае расшифровывается как Remote Forming System.

Давайте рассмотрим подробнее, как именно происходит работа в этом классе. 

Но для начала, в основной базе (конфигурации) 1С, необходимо заготовить одну функцию. Она понадобится для получения из вне, структуры хранения в базе данных. 

// Получает из структуры хранения наименование таблицы в СУБД(SQL) и наименования всех её полей.
// Предназначена для вызова из других программ, не предназначена для использования внутри 1С. 
// Возвращает строку разделенную точкой с запятой. На первом месте имя таблицы, затем список полей.
//
// Параметры: 
//	ИмяТаблицы - Строка - Наименование таблицы которую необходимо транслировать.
Функция GetTableStructure(ИмяТаблицы) Экспорт
	Попытка
		ОбъектМетаданных = Метаданные.НайтиПоПолномуИмени(ИмяТаблицы); 
		Если Не ОбъектМетаданных = Неопределено Тогда
			СписокПолей = "";
			Объект = Новый Массив;
			Объект.Добавить(ОбъектМетаданных);
			СХ = ПолучитьСтруктуруХраненияБазыДанных(Объект, Истина);
			Для Каждого ТекТаб Из СХ Цикл
				Если ТекТаб.Метаданные = ИмяТаблицы Тогда
					НаименованиеТаблицы = ТекТаб.ИмяТаблицыХранения;
				КонецЕсли;
				Для Каждого ТекПоле Из ТекТаб.Поля Цикл
					СписокПолей = СписокПолей + ";" + ТекПоле.ИмяПоляХранения;
				КонецЦикла;
				Возврат НаименованиеТаблицы + СписокПолей;
			КонецЦикла;
		Иначе
			Возврат "";
		КонецЕсли;
	Исключение
		ЯО.ВЛог(ОписаниеОшибки(), "TSQLMethods.GetTableStructure");
		Возврат "";
	КонецПопытки;
КонецФункции

Далее в классе RFS, в методе Execute пишем следующее.

                //Получим наименование таблицы статусов удаленного запуска
                Logger.Log(@"Получаем наименование таблицы рег.сведений УдаленноеФормированиеБДН");
                My1C Com1C = new My1C(AppPath);
                string StateTablesName = Com1C.GetTableStructure(WorkDBComConnect, @"РегистрСведений.УдаленноеФормированиеБДН");
                Com1C.Dispose();
                Com1C = null;

                //Отметимся в рабочей базе о том что процесс формирования запущен успешно
                Logger.Log(@"Установили статус в рабочей базе означающий что все запустилось");
                SetGoodStateOnMainServer(WorkUDLFile, StateTablesName);

В этом фрагменте кода видно как мы используем выше описанную функцию 1С. Она овзращает в виде строки наименование таблицы на уровне SQL, так же и список её полей. Получив имя физической таблицы на уровне SQL мы можем проставить в регистре сведений рабочей базы, флаг об успешном старте работы RFS. Используем для этого SQL скрипт Update.

Выглядеть это будет вот так:

        /// <summary>
        /// Метод проставляет в рабочей базе 1С, в регистре сведений "УдаленноеФормированиеБДН" признак "ПроцессЗапущен" в значение истина. Для всех существующих записей.
        /// </summary>
        /// <param name="UDLFile">Имя файла с настройками подключения к рабочей базе данных</param>
        /// <param name="TableStruct">Строка с физическим наименованием таблицы и её полей через ";"</param>
        private void SetGoodStateOnMainServer(string UDLFile, string TableStruct)
        {
            try
            {
                string[] TableNames = TableStruct.Split(';');
                string QueryText = String.Format(@"UPDATE {0} SET {2} = 0x01 where {2} = 0x00", TableNames);
                OleDbConnection con = new OleDbConnection(String.Format("File Name = {0}", UDLFile));
                OleDbCommand cmd = new OleDbCommand();

                cmd.Connection = con;
                con.Open();
                cmd.CommandType = CommandType.Text;
                cmd.CommandText = QueryText;
                cmd.CommandTimeout = 0;
                cmd.ExecuteScalar();
            }
            catch (Exception err)
            {
                string mess = new ExceptionMessages().GetExMess(err);
                Logger.Log(@"RFS->SetGoodStateOnMainServer() error: " + mess);
            }
        }

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

                //Запустим процесс создания Backup'а на рабочем сервере
                Logger.Log(@"Запущен процесс создания бэкапа рабочей базы");
                string BackupFileName = @"";
                bool WeHaveBackup = CreateFreshBK(WorkUDLFile, out BackupFileName);

Код функции CreateFreshBK не имеет ничего особенного но все же приведу его текст. 

        /// <summary>
        /// Метод создает на рабочем сервере БД, новый, самый свежий бэкап и помещает его в специальную, публичную директорию для скачивания на локальный.
        /// </summary>
        /// <param name="UDLFile">Имя UDL файла с настройками подключения к рабочей базе данных</param>
        /// <param name="FileName">Имя файла создаваемого бэкапа. Имя возвращается наружу для дальнейшего использования.</param>
        /// <returns>Если бэкап удалось создать - возвращается true</returns>
        private bool CreateFreshBK(string UDLFile, out string FileName)
        {

            string BackUpFileName = @"upp_eurasia_" + DateTime.Now.ToString(@"yyyy-MM-dd_hhmmss") + @".bak";
            FileName = BackUpFileName; //Вернем наружу имя файла бэкапа

            try
            {
                Logger.Log(@"Создаем новый бэкап рабочей базы данных в: " + MainServerCreateBackUpToPath + @"\" + BackUpFileName);

                string[] Params = new string[2];
                Params[0] = WorkDBConnect.DataBase;
                Params[1] = MainServerCreateBackUpToPath + @"\" + BackUpFileName;

                string query = String.Format(@"
                BACKUP DATABASE[{0}]
                TO DISK = N'{1}'
                WITH COMPRESSION, 
                INIT,
                NOFORMAT, 
                SKIP, 
                STATS = 10
                ", Params);

                string QueryText = query;
                OleDbConnection con = new OleDbConnection(String.Format(@"File Name = {0}", UDLFile));
                OleDbCommand cmd = new OleDbCommand();
                cmd.Connection = con;
                con.Open();
                cmd.CommandType = CommandType.Text;
                cmd.CommandText = QueryText;
                cmd.CommandTimeout = 0; 
                cmd.ExecuteScalar();

                return true;

            }
            catch (Exception err)
            {
                string mess = new ExceptionMessages().GetExMess(err);
                Logger.Log(@"RFS->CreateFreshBK() error: " + mess);
                return false;
            }
        }

После того как отработает метод создания бэкапа, проверим, был ли он создан успешно. Если все прошло успешно, продолжаем основное действие.

                // Если бэкап базы был успешно создан - продолжаем дальнейшие действия
                if (WeHaveBackup)
                {
                    //Перенесем созданный бэкап на текущий сервер
                    Logger.Log(@"Переносим свежий бэкап на локальный сервер");
                    FileInfo BackupFile = new FileInfo(MainServerBackUpPublicPath + @"\" + BackupFileName);
                    if (BackupFile.Exists)
                    {
                        BackupFile.MoveTo(LocalBackupPath + @"\" + BackupFileName);
                    }

                    //Зачистим все активные подключения к базе данных перед накатыванием бэкапа.
                    Logger.Log(@"Перед разверткой свежего бэкапа, удалим все существующие соединения с базой для формирования отчетности");
                    KillAllDBConnections(MasterUDLFile, ReportsDBConnect.DataBase);

                    //Развернем бэкап на базу для формирования регистров ЯО
                    Logger.Log(@"Запустили восстановление базы из бэкапа");
                    bool DB_Is_Restored = RestoreDB(MasterUDLFile, ReportsDBConnect.DataBase);

                    //Удалим файл бэкапа из которого подняли базу для формирования
                    FileInfo localBackupFile = new FileInfo(LocalBackupPath + @"\" + BackupFileName);
                    if (localBackupFile.Exists)
                    {
                        localBackupFile.Delete();
                    }

                    if (DB_Is_Restored)
                    {
                        //Запустим процесс формирования ЯО на местной базе
                        Com1C = new My1C(AppPath);
                        Logger.Log(@"Выставим корректные настройки подключения к БД для тестовой базы.");
                        //Выставим корректные настройки подключения к БД для тестовой базы.
                        Com1C.SetTestDBConnectionFor1C(ReportsDBComConnect, ReportsDBServerName);
                        Logger.Log(@"Запускаем на тестовой базе формирование регистров ЯО.");
                        //Запустим формирования БДН/ЯО и выгрузку отчетов.
                        Com1C.StartFormationReportCore(ReportsDBComConnect, ReportDay, @"", @"", @"");

                        //Проверим статус формирования в базе формирования отчености. Убедимся что формирование прошло успешно.
                        FormationgState state = (FormationgState)Com1C.GetState(ReportsDBComConnect, ReportDay);
                        Com1C.Dispose(); // больше нам не нужно COM соединение с базой
                        Com1C = null;

                        if (state == FormationgState.fsOK) // Перенесем данные за период с тестовой базы на боевую, при условии что все удачно сформировалось.
                        {
                            //Когда все сформированно выгрузим все необходимые регистры в спец. директорию с помощью bcp
                            Logger.Log(@"Экспортируем данные в файлы");
                            ExportData(ReportDay, ReportsDBConnect, ReportsDBComConnect);

                            //Если стоит соответствующая настройка - перенесем выгруженные данные на рабочую базу данных
                            if (NeedExportDataToWorkBase)
                            {
                                Logger.Log(@"Импортируем данные из файлов в рабочую базу");
                                ImportData(ReportDay, WorkDBConnect, WorkDBComConnect);
                            }
                            else
                            {
                                Logger.Log(@"Импорт данных в рабочую базу - отключен");
                            }
                        }
                        else
                        {
                            SMSBox.SendSMS(@"RFS State not fsOK! Data not migrated!");
                        }
                    }
                    else
                    {
                        Logger.Log(@"Так как не удалось восстановить базу из свежего бэкапа, дальнейшие действия - не возможны. Работа программы - завершена.");
                        SMSBox.SendSMS(@"RFS can't restore DB.");
                        return false;
                    }

                }
                else
                {
                    SMSBox.SendSMS(@"RFS: No backup Work stopped.");
                    return false;
                }

Как видно, сначала мы переносим созданный бэкап на локальный (альтернативный) сервер (от куда запущено приложение). 

Далее мы зачищаем все потенциальные активные сеансы подключения к базе данных, на которую будем восстанавливать свежий бэкап. Делается это с помощью не хитрого запроса. Но надо понимать что не всегда это на 100% помогает. Поэтому эта база данных не должна использоваться кем-то из вне в качестве дополнительной тестовой базы данных. 

        /// <summary>
        /// Убивает все подключения к базе на уровне MS SQL.
        /// </summary>
        /// <param name="UDLFile">Путь к UDL файлу с подключением к базе данных</param>
        /// <param name="DBName">Имя базы данных, подключения к которой - необходимо зачистить.</param>
        /// <returns>Если все прошло успешно - возвращает истину.</returns>
        private bool KillAllDBConnections(string UDLFile, string DBName)
        {
            try
            {
                string QueryText = String.Format(@"
                --Убиваем все активные сеансы подключенные к базе NameDB
                DECLARE @spid VARCHAR(200)
                DECLARE @kill_spid VARCHAR(200)
                DECLARE kill_session CURSOR FOR
                SELECT spid FROM [master].dbo.sysprocesses
                WHERE dbid=db_id('{0}')
                --spid идентификатор сеанса SQL Server.
                OPEN kill_session;
                FETCH NEXT FROM kill_session INTO @spid
                WHILE @@FETCH_STATUS = 0
                BEGIN
                    SET @kill_spid='KILL ' + @spid + char(10)
                    EXEC (@kill_spid)
                    FETCH NEXT FROM kill_session
                    INTO @spid;
                END;
                DEALLOCATE kill_session;", DBName);
                OleDbConnection con = new OleDbConnection(String.Format(@"File Name = {0}", UDLFile));
                OleDbCommand cmd = new OleDbCommand();
                cmd.Connection = con;
                con.Open();
                cmd.CommandType = CommandType.Text;
                cmd.CommandText = QueryText;
                cmd.CommandTimeout = 0; 
                cmd.ExecuteScalar();
            }
            catch (Exception err)
            {
                string mess = new ExceptionMessages().GetExMess(err);
                Logger.Log(@"EFS->KillAllDBConnections() error: " + mess);
                return false;
            }
            return true;
        }

Сам процесс восстановления базы тоже не хитрый. Смотрим далее

        /// <summary>
        /// Накатывает на указанную базу данных - последний актуальный бэкап рабочей базы.
        /// </summary>
        /// <param name="UDLFile">Имя UDL файла с настройками подключения к SQL инстансу, на котором числится база что необходимо восстановить из свежего бэкапа.</param>
        /// <param name="DBName">Имя базы данных - которую необходимо восстановить из бэкапа.</param>
        /// <returns>Если восстановление из бэкапа прошло успешно - возвращает истину.</returns>
        public bool RestoreDB(string UDLFile, string DBName)
        {

            bool result = false;

            string BackUpFileName = @"";

            try
            {
                string[] pfileslocal = Directory.GetFiles(LocalBackupPath, @"*.bak", SearchOption.TopDirectoryOnly);
                foreach (string pf in pfileslocal)
                {
                    BackUpFileName = pf.ToString();
                    break;
                }
                if (BackUpFileName != "")
                {
                    FileInfo fi = new FileInfo(BackUpFileName);
                    if (!fi.Exists)
                    {
                        result = false;
                        return result;
                    }
                }
                else
                    return false;

                Logger.Log(@"Восстанавливаем базу данных из бэкапа: " + BackUpFileName);

                string[] Params = new string[5];
                Params[0] = DBName;
                Params[1] = BackUpFileName;
                Params[2] = ReportsSQLDataPath;
                Params[3] = LogicalDBName;
                Params[4] = LogicalDBLogName;

                string query = String.Format(@"RESTORE DATABASE [{0}]
                FROM DISK = N'{1}'
                WITH FILE = 1,
                MOVE N'{3}' TO
                N'{2}\{0}.mdf',  
                MOVE N'{4}' TO
                N'{2}\{0}_log.ldf',  
                NOUNLOAD,  
                REPLACE,  
                STATS = 10", Params);

                string QueryText = query;
                OleDbConnection con = new OleDbConnection(String.Format(@"File Name = {0}", UDLFile));
                OleDbCommand cmd = new OleDbCommand();
                cmd.Connection = con;
                con.Open();
                cmd.CommandType = CommandType.Text;
                cmd.CommandText = QueryText;
                cmd.CommandTimeout = 0; 
                cmd.ExecuteScalar();

                return true;

            }
            catch (Exception err)
            {
                string mess = new ExceptionMessages().GetExMess(err);
                Logger.Log(@"RFS->RestoreDB() error: " + mess);
                return false;
            }
        }

Как известно, во всем должен быть порядок, поэтому удаляем за собой файл свежего бэкапа, сразу после того как восстановили его на нашу базу данных.

После того как убедились, что база данных успешно была восстановлена, запускаем нашу обработку с помощью COM соединения. В моем примере есть еще некий метод "SetTestDBConnectionFor1C", не уверен что он Вам пригодится, поэтому не буду его рассматривать подробно. Если в крации, то в нашей базе, для вот этой самой обработки я использую вставку и удаление данных напрямую через SQL скрипты, поэтому восстановив базу данных на другом сервере, важно изменить параметры подключения к базе данных SQL, что бы работа вставки и удаления производилась именно на текущей базе данных, а не на рабочей по сетевому подключению SQL. 

В самой 1С, у меня так же имеется регистр сведений, куда пишется флаг об успешном завершении моей обработки, поэтому в конце я проверяю что этот флаг установлен. Потому как если его нет, значит в процессе могла возникнуть ошибка, и тогда необходимо об этом уведомить меня, что бы я заблаговременно начал с этим разбираться. Разумеется это больше для перестраховки. И разумеется если была ошибка, нет большого смысла переносить данные в рабочую базу данных. А вот если все в порядке, то начинаем процесс переноса таблиц средствами BCP (bulk copy program). Как это происходит? Очень просто. Для этого у меня заготовлено два метода:

        /// <summary>
        /// Основной метод экспорта таблицы базы данных в файл с помощью утилиты bcp.
        /// </summary>
        /// <param name="RegName">Наименование регистра 1С, который необходимо выгрузить.</param>
        /// <param name="FileID">Идентификатор регистра 1С, который будет содержаться в наименовании файла, в который будут выгружаться данные.</param>
        /// <param name="InfoDate">Дата сведений, на которую необходимо произвести выгрузку.</param>
        /// <param name="ConnectionString">Строка подключения к базе данных.</param>
        /// <param name="Connect">Параметры подключения к базе данных (SQL).</param>
        /// <param name="ComConnect">Параметры подключения к базе данных (COM).</param>
        /// <param name="DateFilterFieldIndex">Индекс поля, по которому будет происходить отбор по периоду. Значение по умолчанию = 0 (_Period) (Необходимо для не переодических регистров)</param>
        private void ExportTable(string RegName, string FileID, DateTime InfoDate, string ConnectionString, DBConnectStruct Connect, DBConnectStruct ComConnect, int DateFilterFieldIndex = 0)
        {
            try
            {
                string DateFilterFieldName = @"_Period";
                My1C etCom1C = new My1C(AppPath);
                if (DateFilterFieldIndex != 0)
                {
                    string TableNameAndField = etCom1C.GetTableStructure(ComConnect, RegName);
                    string[] Fiels = TableNameAndField.Split(';');
                    DateFilterFieldName = Fiels[DateFilterFieldIndex];
                }
                string QueryText = @"Select * from " + Connect.DataBase + @".dbo." + etCom1C.GetTableStructure(ComConnect, RegName, true) + @" where " + DateFilterFieldName + " = " + QuotedStr(InfoDate.ToString("yyyy-MM-dd"), "'");
                string FileName = BCPTransitPath + @"\" + bcpFilePrefix + "_" + FileID + "_" + DateTime.Now.ToString(bcpFileMask) + ".bcp";
                string CommandBCP = GetExportBCPCommandText(QueryText, FileName, ConnectionString);
                ExecuteAndWait(@"bcp ", CommandBCP);
                etCom1C.Dispose();
            }
            catch (Exception err)
            {
                string mess = new ExceptionMessages().GetExMess(err);
                Logger.Log(@"RFS->ExportTable() error: " + mess);
            }
        }

        /// <summary>
        /// Основной метод импорта таблицы базы данных из файла с помощью утилиты bcp.
        /// </summary>
        /// <param name="RegName">Наименование регистра 1С, который необходимо загрузить из файла.</param>
        /// <param name="FileID">Идентификатор регистра 1С, который будет содержаться в наименовании файла, из которого будут загружаться данные.</param>
        /// <param name="InfoDate">Дата сведений, на которую необходимо произвести загрузку данных.</param>
        /// <param name="ConnectionString">Строка подключения к базе данных.</param>
        /// <param name="Connect">Параметры подключения к базе данных (SQL).</param>
        /// <param name="ComConnect">Параметры подключения к базе данных (COM).</param>
        /// <param name="UDLConnect">Наименование UDL файла с параметрами подключения к базе, в которую будет производиться импорт данных.</param>
        /// <param name="DateFilterFieldIndex">Индекс поля, по которому будет происходить отбор по периоду. Значение по умолчанию = 0 (_Period) (Необходимо для не переодических регистров)</param>
        private void ImportTable(string RegName, string FileID, DateTime InfoDate, string ConnectionString, DBConnectStruct Connect, DBConnectStruct ComConnect, string UDLConnect, int DateFilterFieldIndex = 0)
        {
            try
            {
                string DateFilterFieldName = @"_Period";
                My1C itCom1C = new My1C(AppPath);
                if (DateFilterFieldIndex != 0)
                {
                    string TableNameAndField = itCom1C.GetTableStructure(ComConnect, RegName);
                    string[] Fiels = TableNameAndField.Split(';');
                    DateFilterFieldName = Fiels[DateFilterFieldIndex];
                }
                string SQLTableName = Connect.DataBase + @".dbo." + itCom1C.GetTableStructure(ComConnect, RegName, true);
                itCom1C.Dispose();
                string QueryText = @"WITH CTE_DELETE AS (SELECT * FROM " + SQLTableName + @" where " + DateFilterFieldName + " = " + QuotedStr(InfoDate.ToString("yyyy-MM-dd"), "'") + ") Delete From CTE_DELETE"; //Запрос для удаления записей, которые будем сейчас вставлять. 
                OleDbConnection con = new OleDbConnection(String.Format(@"File Name = {0}", UDLConnect));
                OleDbCommand cmd = new OleDbCommand();
                cmd.Connection = con;
                con.Open();
                cmd.CommandType = CommandType.Text;
                cmd.CommandText = QueryText;
                cmd.CommandTimeout = 0; 
                cmd.ExecuteScalar();

                string FileName = BCPTransitPath + @"\" + bcpFilePrefix + "_" + FileID + "_" + DateTime.Now.ToString(bcpFileMask) + ".bcp";
                string CommandBCP = SQLTableName + @" in " + GetImportBCPCommandText(FileName, ConnectionString);

                ExecuteAndWait(@"bcp ", CommandBCP);
            }
            catch (Exception err)
            {
                string mess = new ExceptionMessages().GetExMess(err);
                Logger.Log(@"RFS->ImportTable() error: " + mess);
            }
        }
		

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

Вот собственно и все. Данные успешно сформированы и перенесены. Если же что-то пошло не так, ответственные люди получают СМС (при наличии такой возможности в компании), либо e-mail c предупреждением об ошибке. 

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

Изначально я планировал выгружать их так же через COM, но почему-то именно с отчетами возникли проблемы, в процессе выгрузки, COM-соединение рвалось и процесс умолкал. Когда появится время - думаю я найду верное решение. И постараюсь найти время что бы им поделиться. Но пока и регламентное задание не плохо справляется, конечный результат - есть.

p.s.

Изначально часть этого проекта была рождена как аварийная система. И срабатывала только в том случае, если по какой-то причине на рабочей базе обработка завершалась неудачей, приходило уведомление об этом, а тем временем на тестовом сервере запускался процесс аналогичный выше описанному. Но в итоге все переросло в эдакую систему удаленной обработки данных. 

Безусловно такие внедрения не всегда и не везде рациональны, и не всегда стоят того что бы их делать. Но в редких случаях - это решает проблемы. 

К статье прикрепляю архив с основными классами на C#. Весь проект целиком, разумеется, выложить не могу по вполне ясным причинам. 

Всем добра! :)

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

Скачать файлы

Наименование Файл Версия Размер
Архив с исходным кодом класса My1C для C#, ряд вспомогательных классов. Может выступить в качестве шаблона для нового проекта. Не универсальное решение.

.zip 8,31Kb
08.02.19
1
.zip 1.0 8,31Kb 1 Скачать

Специальные предложения

Комментарии
Избранное Подписка Сортировка: Древо развёрнутое
Свернуть все
1. Lem0n 198 08.02.19 16:16 Сейчас в теме
"Еще больший интерес предоставляет возможность запускать «только фоновые задания» на рабочем сервере кластера без сеансов пользователей. Таким образом можно высоконагруженные задачи (код) вынести на отдельный машины. При чем можно одно фоновое задание «закрытия месяца» через «Значение дополнительного параметра» запускать на одном компьютере, а фоновое задание «Обновление полнотекстового индекса» на другом.Уточнение происходит через указание «Значение дополнительного параметра». Например если указать BackgroundJob.CommonModule в качестве значения, то можно ограничить работу рабочего сервера в кластере только фоновыми заданиями с любым содержимым. Значение BackgroundJob.CommonModule.<Имя модуля>.<Имя метода> — укажет конкретный код"
2. ixilimuse 174 11.02.19 06:16 Сейчас в теме
(1) Безусловно кластеризация 1С крутая штука, но попробую влить ложку дегтя. Если где-то буду не прав - исправьте меня.

1. Тот самый сервер, который сейчас я использую для того, что описано в посте, мы заказывали как раз для кластеризации. Была такая идея фикс улучшить производительность и отказоустойчивость. На деле, мы не ощутили значимого прироста в производительности, а вот в стабильности даже потеряли. В чем это проявилось? Наша база (конфигурация) обновляется минимум 1 раз в сутки, а бывает что 2-3 раза в сутки. (Вынуждены подстраиваться под бизнес очень динамично) Так вот, очень часто возникал баг, после обновления конфигурации, 1С переставала запускаться. Для фикса приходилось останавливать сервер приложений на обоих серверах, перезапускать SQL серверную службу, и лишь после этого все оживало. Это было накладно по времени, особенно в тех случаях, когда обновление необходимо сделать в рабочее время, и тут возникает такой глюк. В результате от кластеризации отказались.
(Тут конечно есть такой фактор как версия... Возможно в последней версии это исправлено, но мы, увы, ради стабильности не прыгаем на каждый новый релиз платформы во избежании испытывать на себе все новые баги. Ибо нас за это не похвалят. Поэтому, на текущий момент положение дел такое, что мы не используем кластеризацию по вполне объяснимым причинам.)
2. Помимо пункта 1, все таки, кластеризация - подразумевает распределение процессорной нагрузки и нагрузки по ОЗУ. Но при этом по прежнему БД остается тонким местом. И в моем примере, мой запрос вытягивает приблизительно от 3 до 5 млн записей (на текущий момент, но это количество неуклонно растет с каждым месяцем), затем часть этих данных распределяется по регистрам. Это хорошо если это все запущено ночью и лишь 2-3 человека в этот момент могут сидеть и получать тоже довольно увесистые отчеты. Но допустим бывают ситуации когда все же необходимо эту "мега обработку" запустить днем. Днем в базе работают все филиалы со всех городов. Все они дружно заключают договора, получают отчеты, тем временем из разных источников интеграционными методами в базу льются тысячи договоров и заключаются (проводятся) автоматически, что в общей массе создает не слабую очередь, периодические блокировки. Засим, выполнять мою "мега обработку", куда более приятно и спокойно, на отдельной базе данных, где будет работать только моя обработка.

p.s. У нас еще есть одна идея фикс, это сделать зеркалирование БД, и для отчетов использовать зеркало, а запись вести в основную базу (диск). Но тут тоже море подводных граблей, и как пишут опытные в этом деле люди, жизнь зеркалированной БД для 1С длится ровно до первой реструктуризации БД. И поэтому для нас это вообще не подходит) Слишком часто придется сталкиваться с лишними проблемами. Поэтому сложившихся условиях, то что описано в посте, очень помогает и разгружает наше решение. Но разумеется это не панацея и не в любых решениях будет оправдано.
3. A_Max 18 11.02.19 14:21 Сейчас в теме
(2)
p.s. У нас еще есть одна идея фикс, это сделать зеркалирование БД, и для отчетов использовать зеркало

Скоро это будет уже в типовых механизмах, т.ч. эксперименты можно отложить. 1С озвучила эту фичу ещё весной прошлого года на партнёрской конференции.
ixilimuse; +1 Ответить
4. ixilimuse 174 11.02.19 15:07 Сейчас в теме
(3) Благодарю за наводку, будем ожидать с трепетом и надеждами =) Экспериментировать даже и не пытались, так как предварительная разведка показала, что шансов на успех маловато)
5. DonAlPatino 142 12.02.19 15:09 Сейчас в теме
(3)Учитывая, что весна этого года уже на носу, то "обещанного три года ждут?" :-)
6. DonAlPatino 142 12.02.19 15:09 Сейчас в теме
(1)Вроде это только в корпоративной версии работает?
Оставьте свое сообщение

См. также

Рабочий стол зарплатчика Промо

Зарплата Рабочее место v8 v8::СПР v8::УФ ЗУП3.x Россия БУ Абонемент ($m)

Назначение обработки: - Консолидация основных операций работника кадровой службы, расчетчика заработной платы в одном окне; - Весь цикл кадровых операций: от создания сотрудника, до ввода любого необходимого кадрового документа; - Ввод зарплатных документов: плановых начислений, удержаний; ввода документов расчета, перерасчета, выплаты зарплаты; ввода налоговых документов.

15.01.2020    4897    4    HostHost    0    

Загрузка табелей рабочего времени из файлов Excel

Зарплата Обработка документов Учет рабочего времени Загрузка и выгрузка в Excel v8 v8::СПР ЗУП3.x Россия БУ Абонемент ($m)

Обработка по загрузке в типовой документ «Табель» конфигурации ЗУП 3.1 данных из файлов MS Excel, согласно шаблону.

18.10.2019    7393    5    HostHost    0    

Альтернативный способ добавления элементов и реквизитов на формы

Работа с интерфейсом v8 ERP2 УТ11 Россия Абонемент ($m)

Предлагаю альтернативный вариант добавления динамически создаваемых элементов и реквизитов на форму.

1 стартмани

09.09.2019    8811    10    bmk74    1    

Аналоги номенклатуры для УТ 11.4, КА 2.4, ERP 2.4, Розница 2.2, Розница 8. Магазин автозапчастей. Расширение

Рабочее место Оптовая торговля Розничная торговля Управление торговлей Оптовая торговля Розничная торговля v8 Розница ERP2 УТ11 КА2 Автомобили, автосервисы Оптовая торговля, дистрибуция, логистика Россия УУ Абонемент ($m)

Расширение для работы с аналогами номенклатуры. Отлично подходит для работы с кроссами запчастей и товаров с аналогичными свойствами. Поддерживаемые конфигурации: УТ 11.4, КА 2.4, ERP 2.4, Розница 2.2, Розница 8. Магазин автозапчастей. Лёгкое подключение, без изменения конфигурации.

10 стартмани

30.08.2019    15429    13    AleSSandre    25    

Установка предопределенных элементов: просмотр, исправление и поиск ошибок (задвоенных и отсутствующих) Промо

Инструментарий разработчика Универсальные обработки v8 1cv8.cf Абонемент ($m)

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

2 стартмани

06.10.2014    145183    1951    ekaruk    164    

Работа с релизами 1С и договорами ИТС

WEB БСП (Библиотека стандартных подсистем) v8 1cv8.cf Абонемент ($m)

Работа с релизами 1С и партнерским кабинетом.

2 стартмани

15.08.2019    12063    34    RocKeR_13    10    

Вам нравятся запросы в 1С?

Практика программирования Разработка v8 v8::Запросы 1cv8.cf Абонемент ($m)

Речь не только о том, что простейший запрос с "легальным" оформлением растянется на пол-экрана, речь еще обо всем, что нужно написать "в нагрузку" к тексту запроса. Все эти "Новый Запрос", "УстановитьПараметр" и последующие пляски с обработкой результата... Пора с этим заканчивать!

1 стартмани

03.07.2019    17240    4    m-rv    86    

Навигатор по конфигурации базы 1С 8.3 Промо

Инструментарий разработчика Универсальные обработки v8 v8::УФ 1cv8.cf Россия Абонемент ($m)

Универсальная внешняя обработка для просмотра метаданных конфигураций баз 1С 8.3. Отображает свойства и реквизиты объектов конфигурации, их количество, основные права доступа и т.д. Отображаемые характеристики объектов: свойства, реквизиты, стандартные рекизиты, реквизиты табличных частей, предопределенные данные, регистраторы для регистров, движения для документов, команды, чужие команды, подписки на события, подсистемы. Отображает структуру хранения объектов базы данных, для регистров доступен сервис "Управление итогами". Платформа 8.3, управляемые формы. Версия 1.1.0.63 от 10.06.2020

3 стартмани

28.10.2018    29277    276    ROL32    67    

Модель объекта

Инструментарий разработчика v8 Абонемент ($m)

Подсистема позволяет описать модель данных объекта, где описана зависимость между реквизитами, и затем использовать эту модель в разных сценариях работы с объектом. Версия платформы: 8.3.6 и выше. С небольшими доработками будет работать на 8.2.

1 стартмани

30.06.2019    10813    0    vadim1980    5    

Цифровая подпись Cades-BES для XML средствами 1С с помощью КриптоПро

Защита и шифрование v8 1cv8.cf Россия Абонемент ($m)

Обработка иллюстрирует возможность подписания XML SOAP-конверта по стандарту Cades-BES средствами 1С с помощью внешней компоненты КриптоПРО "CAdESCOM" с учетом ГОСТ 2001 и ГОСТ 2012. Стандарт используется в различных механизмах государственных сайтов России, в том числе в СМЭВ и ГИС ЖКХ. Код не привязан к прикладному решению может быть встроен куда угодно, но только на платформе Windows.

1 стартмани

13.05.2019    9345    38    PythonJ    41    

Быстрый запрос

Универсальные обработки v8 v8::УФ 1cv8.cf Абонемент ($m)

Можно ли дать пользователю "удочку", а не "рыбу"? До сих пор ответ на этот вопрос был отрицательным. Всякий инструмент, который мог бы делать с базой данных все или почти все (или хотя бы многое), отвергался пользователями, как слишком сложный. Вспомните тот же SQL, который изначально разрабатывался именно как пользовательский инструмент. "Быстрый запрос" - это попытка устранить сложность, но сохранить при этом универсальность.

1 стартмани

29.04.2019    13734    25    mkalimulin    28    

Печатные формы для БП 3.0: УПД, ТОРГ-12, Акт, Счет-фактура, Доверенность, Счет Промо

Печатные формы документов Оптовая торговля Производство готовой продукции (работ, услуг) Оптовая торговля Производство готовой продукции (работ, услуг) v8 v8::БУ БП3.0 Россия БУ Абонемент ($m)

Внешние печатные формы УПД, ТОРГ-12, Акта об оказании услуг, Счет-фактуры, Доверенности и Счета на оплату покупателю, актуализированы на последний релиз 3.0.71, поддерживают типовые факсимиле (новую галочку "Подпись и печать"), не содержат ошибок с новой ставкой НДС. В публикации также представлены печатные формы для устаревших релизов.

1 стартмани

28.01.2016    71456    843    config    47    

Безопасная работа с транзакциями во встроенном языке

Практика программирования v8 1cv8.cf Абонемент ($m)

Разбираемся с опасностями использования транзакций во встроенном языке 1С. Познаем ошибку "В данной транзакции уже происходили ошибки". Учимся защищаться от них.

1 стартмани

25.03.2019    30215    10    tormozit    44    

Трудовой договор, Дополнительное соглашение к трудовому договору, Лист ознакомления, Договор о материальной ответственности, Договор о коммерческой тайне, Согласие на обработку персональных данных для ЗУП 3.1

Печатные формы документов Зарплата Управление персоналом (HRM) Зарплата Управление персоналом (HRM) v8 v8::СПР ЗУП3.x Россия БУ Абонемент ($m)

Комплект печатных форм для отдела кадров для документов Прием на работу и Кадровый перевод: Трудовой договор, Доп. соглашение к трудовому договору, Лист ознакомления с локальными нормативными актами, Договор о полной материальной ответственности, Договор о неразглашении коммерческой тайны, Согласие на обработку персональных данных.

2 стартмани

12.03.2019    27607    145    Asenka    34    

Редактор объектов информационной базы 8.3

Универсальные обработки Обмен через XML v8 v8::УФ 1cv8.cf Россия Абонемент ($m)

Универсальная внешняя обработка для редактирования реквизитов и табличных частей объектов информационной базы, редактирование движений документов. Доступ ко всем реквизитам объектов, есть возможность выгрузки и загрузки данных (объекты и движения документов) через XML. Платформа 8.3, управляемые формы. Версия 1.1.0.47 от 08.06.2020

2 стартмани

23.01.2019    24398    257    ROL32    33    

Универсальная выгрузка/загрузка данных для отличающихся конфигураций (JSON, Такси+ОФ) Промо

Перенос данных из 1C8 в 1C8 Универсальные обработки Распределенная БД (УРИБ, УРБД) v8 1cv8.cf Абонемент ($m)

Простой перенос через JSON данных между двумя базами 1С (документов, справочников, ПВХ, ПВР, счетов). Аналогична произвольной выгрузке в типовой "Выгрузка/загрузка XML", но может использоваться для отличающихся конфигураций. Подходит для любых пар баз с любым интерфейсом (управляемый + обычный). Без настроек. Не требует идентичности конфигураций и платформ. При переносе типы данных сопоставляются по наименованиям метаданных, объекты и ссылки по UID.

1 стартмани

22.10.2014    202256    3078    ekaruk    178    

Расширение "Курсы валют в формулах расчета динамических цен" для УНФ 1.6

Ценообразование, прайсы Ценообразование, анализ цен Ценообразование, анализ цен v8 УНФ УУ Абонемент ($m)

Расширение "Курсы валют в формулах расчета динамических цен" с автоматическим пересчетом цен при изменении курсов валют для конфигурации "Управление нашей фирмой, редакция 1.6"

5 стартмани

17.01.2019    13464    20    Palmer1976    6    

Конструктор мобильного клиента Simple WMS Client: способ создать полноценный ТСД без мобильной разработки. Теперь новая версия - Simple UI (обновлено 14.11.2019)

Инструментарий разработчика Сканер штрих-кода Терминал сбора данных Мобильная разработка Оптовая торговля Производство готовой продукции (работ, услуг) Розничная торговля Учет ОС и НМА Учет ТМЦ Оптовая торговля Производство готовой продукции (работ, услуг) Розничная торговля Учет ОС и НМА Учет ТМЦ v8 v8::Mobile БУ УУ Абонемент ($m)

Simple WMS Client – это визуальный конструктор мобильного клиента для терминала сбора данных(ТСД) или обычного телефона на Android. Приложение работает в онлайн режиме через интернет или WI-FI, постоянно общаясь с базой посредством http-запросов (вариант для 1С-клиента общается с 1С напрямую как обычный клиент). Можно создавать любые конфигурации мобильного клиента с помощью конструктора и обработчиков на языке 1С (НЕ мобильная платформа). Вся логика приложения и интеграции содержится в обработчиках на стороне 1С. Это очень простой способ создать и развернуть клиентскую часть для WMS системы или для любой другой конфигурации 1С (УТ, УПП, ERP, самописной) с минимумом программирования. Например, можно добавить в учетную систему адресное хранение, учет оборудования и любые другие задачи. Приложение умеет работать не только со штрих-кодами, но и с распознаванием голоса от Google. Это бесплатная и открытая система, не требующая обучения, с возможностью быстро получить результат.

5 стартмани

09.01.2019    39216    245    informa1555    200    

Загрузка данных из М-Аптеки+ в 1С:Бухгалтерия 8 (ред. 3.0) Промо

Внешние источники данных Файловые протоколы обмена, FTP v8 v8::БУ БП3.0 Фармацевтика, аптеки БУ Абонемент ($m)

Обработка для загрузки файлов выгрузки из программы для автоматизации аптек и аптечных сетей М-Аптека+ в 1С:Бухгалтерия 8, редакция 3.0.

11.07.2014    32440    28    1C_MApteka    37    

Сравнение pdf-файлов актов сверки

Универсальные обработки Дебиторская и кредиторская задолженность Дебиторская и кредиторская задолженность v8 v8::БУ БП2.0 Россия БУ Абонемент ($m)

Обработка сравнивает два pdf-файла, в которых находятся стандартные печатные формы актов сверки, и показывает на экране совпадающие и/или отличающиеся по суммам документы взаиморасчетов.

1 стартмани

19.12.2018    15448    6    Torin99    2    

Выгрузка изображений из справочника на диск

Учет ТМЦ Универсальные обработки Учет ТМЦ v8 УТ11 Оптовая торговля, дистрибуция, логистика Россия Абонемент ($m)

Обработка позволяет записать изображения из справочка Номенклатура на диск в указанное место.

1 стартмани

30.11.2018    15159    5    wrooom    8    

Проверка VAT номеров

WEB v8 1cv8.cf Абонемент ($m)

Обработка для вызова сервиса проверка VAT номера.

1 стартмани

26.11.2018    10573    0    wtlz    1    

Внешняя компонента для работы по Web-socket протоколу Промо

Разработка внешних компонент WEB v8 Абонемент ($m)

Кто когда-нибудь сталкивался с обменом данными по Web-Socket (wss) протоколу из 1С, тому известно, что в платформе отсутствуют данные механизмы (не путать с HTTP запросами и WebServices). Предлагается использовать внешнюю компоненту, написанную по технологии NativeAPI, для подключения и обмена с серверами из 1С-Предприятия, работающими по протоколу Web-Socket.

5 стартмани

30.03.2018    22952    32    Ditron    68    

Обнуление остатков регистров бухгалтерии и накопления

Универсальные обработки Чистка базы v8 v8::БУ v8::ОУ v8::УФ КА1 БП2.0 ЗУП2.5 УТ10 УПП1 УНФ БГУ ERP2 БП3.0 УТ11 УХ КА2 ЗУП3.x Россия Абонемент ($m)

Обработка позволяет обнулить остатки по регистру накопления или бухгалтерии на определенную дату. Поддерживается большинство типовых конфигураций (БП 3, БП 2, УТ 11, УТ 10, ЗУП 3, ЗУП 2, БГУ 2, БГУ 1, ERP, УПП, КА 2, КА 1, УХ 3, УХ 1, УНФ). Гибкая настройка (отборы, заполнение реквизитов и любых полей корр. счета, возможность обнулять ресурсы выборочно). Несколько режимов работы. Два интерфейса: простой и с расширенным набором настроек.

2 стартмани

19.11.2018    19763    258    morozov.sv    34    

Шпаргалка разработчика для работы с формами

Работа с интерфейсом v8 Россия Абонемент ($m)

Сборник помогает быстро освоить встроенный язык и средства конфигурирования при разработке обычных и управляемых форм.

3 стартмани

31.10.2018    14489    77    ELAM    3    

[Расширение] Контроль отрицательных остатков по регистру бухгалтерии при проведении Промо

Универсальные функции Инструментарий разработчика Учет ТМЦ Учет ТМЦ v8 1cv8.cf Россия БУ Абонемент ($m)

Расширение позволяет без изменений конфигурации проверять остатки по регистру бухгалтерии при проведении каждого документа и запрещает проведение при появлении отрицательных остатков после проведения.

1 стартмани

17.08.2015    44809    155    ekaruk    31    

Открывашка ячеек таблиц

Работа с интерфейсом v8 1cv8.cf Абонемент ($m)

Глобальное сочетание клавиш для открытия объекта по ссылке из текущей ячейки любой таблицы в большинстве управляемых форм

1 стартмани

27.10.2018    14973    12    tormozit    31    

Универсальный инструмент для переноса данных через табличный документ (УФ)

Обработка документов Универсальные обработки Обработка справочников v8 v8::УФ 1cv8.cf Абонемент ($m)

Для опытных пользователей, разработчиков, администраторов, консультантов

5 стартмани

15.10.2018    29689    114    json    35    

Расширение "Интерфейс Плюс"

Розничная торговля Рабочее место Розничная торговля v8 v8::ОУ Розница УТ11 Россия УУ Абонемент ($m)

Расширение для 1С:Розница 2.2 и 1С:Управление Торговлей 11, которое позволит повысить удобство работы!

3 стартмани

22.09.2018    22650    117    RocKeR_13    105    

Менеджер загрузки данных из весов с печатью этикеток Промо

Внешние источники данных Весы Учет ТМЦ Учет ТМЦ v8 УТ10 УПП1 Абонемент ($m)

Программное обеспечение "Менеджер загрузки данных из весов с печатью этикеток" предназначено для автоматического получения данных по сети Ethernet из весов МАССА-К моделей ВПМ и ТВ_Р3 (модификация MF) в режиме On-Line.

1 стартмани

20.12.2011    20031    3    hrip    10    

Отправка электронной почты с помощью локального почтового клиента из 1С, развернутой под удаленным рабочим столом

Печатные формы документов Универсальные функции Email v8 КА1 БП2.0 УТ10 УПП1 Абонемент ($m)

Решение для интерактивной (нажал-отредактировал) отправки электронных писем и печатных форм через почтовый клиент (Thunderbird, Outlook) находящийся на локальном компьютере, из конфигурации 1С, развернутой под удаленным рабочим столом (RDP, remote-app). Подходит также для локального развертывания 1С. Представлен пример быстрой интеграции с конфигурациями "Управление торговлей 10.3", "Управление производственным предприятием 1.3", "Комплексная автоматизация 1.1", "Бухгалтерия предприятия 2.0".

1 стартмани

21.09.2018    21229    13    stvorl    0    

TextRadar - нечеткий поиск в тексте

Разработка внешних компонент Поиск данных v8 1cv8.cf Абонемент ($m)

В отличие от нечеткого сравнения строк, когда обе сравниваемых строки равнозначны, в задаче нечеткого поиска выделяются строка поиска и строка данных, а вычислить необходимо не степень похожести двух строк, а степень присутствия строки поиска в строке данных. Нормированный коэффициент, находящийся между 0 и 1 позволяет отсечь заведомо низкие результаты, организовать поиск по синонимам и т.д. Наглядное отображение результатов в кратком и детальном виде поможет быстро найти интересующий фрагмент.

1 стартмани

19.09.2018    15136    17    TSSV    16    

Работа с публикациями "Инфостарт"

Практика программирования О сообществе WEB v8 УУ Абонемент ($m)

Работа с рублевыми публикациями на сайте "Инфостарт": ведение клиентов, заказов, обновление файлов публикации, рассылка обновлений.

1 стартмани

13.09.2018    18777    12    RocKeR_13    16    

Автоматическая рассылка прайс-листов Промо

Ценообразование, прайсы Email Оптовая торговля Розничная торговля Оптовая торговля Розничная торговля v8 УТ10 Абонемент ($m)

Устали от утомительной рассылки прайс-листов своим клиентам? Тогда эта обработка как раз для вас, она сделает полностью все сама! Сформирует прайс с нужными настройками и отправит его по электронной почте, как, куда и когда надо.

1 стартмани

24.03.2014    36092    41    skyadmin    18    

Позиционирование в помещении с помощью нейросети по сигналу Wi-Fi. Интерактивная карта склада в 1С с показом позиции

Инструментарий разработчика Практика программирования v8 Абонемент ($m)

Данная публикация содержит в себе редактор и интерактивную карту склада или иного помещения, на которой в реальном времени отображается позиция устройства, координаты которого вычисляются по уровням сигнала нескольких роутеров Wi-Fi. В статье и приложенным к ней разработкам предлагаются инструменты и методика для реализации вычисления точной геопозиции внутри помещений с помощью нейронной сети. Конфигурация написана на релизе 1С:Предприятие 8.3.12.1412, клиентское приложение имеет минимальный уровень совместимости SDK -16.

5 стартмани

09.08.2018    25345    25    informa1555    26    

Визуализация событий на временной шкале средствами "Поле HTML документа"

Работа с интерфейсом v8 1cv8.cf Абонемент ($m)

Интересный способ наглядно отобразить события на временной шкале. Например, может быть применен для красивого вывода документов по клиенту. Тестировалось на платформе 8.3.12.1469

1 стартмани

31.07.2018    21438    135    Plotks2017    27    

Работа с данными выбора

Практика программирования Работа с интерфейсом v8 Россия Абонемент ($m)

В управляемом интерфейсе заложена мощная возможность описывать связи реквизитов формы через параметры. Установка параметров связей позволяет ограничить выбор данных так, чтобы целостность данных была обеспечена на этапе ввода. Однако без дополнительного программирования задать можно только самые простые связи. Такие условия связи, как зависимость от реквизита через точку или зависимость через дополнительное отношение, заданное в регистре сведений - уже задать без программирования не получится.

1 стартмани

17.07.2018    39177    17    kalyaka    16    

Пример использования REST API Яндекс Диска

WEB v8 1cv8.cf Абонемент ($m)

Пример использования REST API Яндекс Диска: чтение диска, добавление каталога, загрузка файла, скачивание файлов или каталогов, удаление файлов или каталогов.

1 стартмани

26.06.2018    21533    33    MKFreeUser    14    

Приложение Android для идентификации/распознавания образов (с обучением с одного раза, One-Shot Learning) с возможностью работы с 1С через веб-сервис

Мобильная разработка v8 Абонемент ($m)

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

1 стартмани

19.06.2018    13595    4    informa1555    15    

Обмен файловыми базами данных через Yandex диск

WEB v8 Россия Абонемент ($m)

Выполнение операций обмена с Yandex диском для файловых БД, по протоколу WebDav, в автоматическом или ручном режимах.

1 стартмани

11.06.2018    15422    5    slimper    1    

Конфигурация для просмотра публичных телеграм каналов

WEB v8 Абонемент ($m)

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

1 стартмани

02.06.2018    16063    10    DO_WHILE_LOOP    7    

ВСТАВИТЬ В Справочник.Номенклатура (Код, Наименование) ЗНАЧЕНИЯ ("001", "Новый товар")

Практика программирования v8 v8::Запросы 1cv8.cf Абонемент ($m)

Вас не обманывают ваши глаза, это запрос на изменение данных! И это работает без прямого доступа к БД, регистрации и смс.

1 стартмани

01.06.2018    27548    86    m-rv    57    

БСП: Дополнительная обработка (Регламенты), примеры от простого к сложному

Практика программирования БСП (Библиотека стандартных подсистем) v8 1cv8.cf Абонемент ($m)

Очень много попадается странных решений, которые можно решить через БСП:Дополнительные отчеты и обработки. Я бы вообще БСП из-за этой подсистемы переименовал в «Большое Спасибо Программистам». Поработаем с подсистемой в части написания регламентных заданий.

1 стартмани

10.05.2018    41021    33    dsdred    36    

Tool1CD Beta в деле

Разработка внешних компонент v8 Абонемент ($m)

Пс, парень! Не хочешь немного сырых байтов?

1 стартмани

09.05.2018    23413    28    baton_pk    26    

Как выполнить отчет на СКД через COM и получить данные отчета?

Практика программирования v8 УПП1 Россия Абонемент ($m)

Для чего это нужно. Например, нужно в одной базе получить какой-либо показатель из другой базы. Этот показатель вычисляется в каком-либо сложном отчете, который написан на СКД. Можно, конечно, "скопипастить" текст запроса из другой базы, немного подправить его и выполнять в том же COM подключении. Но с этим теряется гибкость: если отчет изменился, то нужно помнить о том, что где-то есть его "немного модифицированная" копия. В статье будет рассмотрен пример получения данных из базы ЗУП.

2 стартмани

08.05.2018    25656    8    wowik    3