Модуль рассылки SMS-сообщений, исходный текст

page_sms.h, модифицирован 03/06/2022


#include <windows.h>
#include "\works\h\web_server.h"

// *****************
// * SMS-сообщения *
// *****************
const int SMS_VERSION = 4;
struct SMS
{
	int iVersion;
	int iId; // идентификатор
	// тип:
	// 0 - тест
	// 1 - выдача результатов
	// 2 - поздравление с днём рождения
	// 3 - рассылка информации клиентам
	int iType;
	int iStep; // шаг обработки
	// время создания записи
	union
	{
		FILETIME ft_create;
		__int64 time64_create;
	};
	char szDate [11]; // дата создания
	char szTime [5 +1]; // время создания
	char szPrefix [WS_MAX_NAME_LEN +1]; // префикс отправителя
	char szPhone [11 +1]; // номер телефона получателя в формате 71234567890
	char szText [256 +1]; // текст сообщения в кодировке 1251
	int iClientNum; // тип 1,2,3; уникальный номер клиента
	int iContractNum; // тип 1: номер документа
	char szContractDate [11]; // тип 1: дата оформления документа
	bool bAction; // обработка начата
	// время последней обработки
	union
	{
		FILETIME ft_action;
		__int64 time64_action;
	};
	char szStepTime [5 +1];
	// ответ сервиса
	char szServiceAnswer [64 +1];
	int iSmsNum; // идентификатор сообщения
	bool bError; // ошибка, ожидание
	bool bCompleted; // операции завершены
	bool bSuccess; // успешно
	int iGate; // используемый шлюз (0 - не выбран)
	int iErrorCode; // 1000sms.ru
	bool bPaused; // пауза в обработке сообщения
};
#if defined(__WATCOMC__)
extern std::auto_ptr<RING> sms;
#else
extern std::unique_ptr<RING> sms;
#endif // defined

void fnCheckSms(void);
// копирует в dst найденный в szPhones номер сотового телефона в формате 71234567890
bool fnGetPhone(char *dst,char *szPhones);
void fnSendSmsTest(QUERY *query,char *szPhone,char *szText); // тест
void fnSendSmsGivingResults(QUERY *query,int iVisitNum); // выдача результатов
bool fnSendSmsBirthday(QUERY *query,int iVisitNum); // поздравление с днём рождения
bool fnSendSmsInfo(QUERY *query,int iClientNum,char *szText); // информация клиентам
void fnSmsQueueScan(void); // сканер очереди, вызывается из шедулера

page_sms.cpp, модифицирован 06/06/2022


#include <winsock2.h>
#include <windows.h>
#include <stdio.h> // sprintf()
#include <string>
#include "\works\h\maelstrom.h" // fnReplace()
#include "client.h"
#include "config.h"
#include "main.h" // fnLog(), fnGetToday()
#include "other\md5.h"
#include "page_sms.h"
#include "page_tuning_private.h"
#include "pages.h"
#include "read_ini.h"
#include "s_date.h"
#include "s_forms.h"
#include "s_services_groups.h"
#include "visit.h"

using namespace std;
using namespace md5;

// ассоциативные массивы
#if defined(__WATCOMC__)
static std::auto_ptr<A_ARRAY> login_days (new A_ARRAY("page_sms.cpp: login_days")); // "имя входа/кол-во дней"
#else
static std::unique_ptr<A_ARRAY> login_days (new A_ARRAY("page_sms.cpp: login_days")); // "имя входа/кол-во дней"
#endif // defined

void fnCheckSms(void)
{
	char szToday [11];
	int iQty;
	int iCycle;
	SMS s_sms;
	bool bWorkNeed;

	fnGetToday(szToday);
	iQty = sms->fnGetQty();

	// определяем необходимость повышения версии записей
	bWorkNeed = false;
	for(iCycle = 0; iCycle < iQty; iCycle++)
	{
		sms->fnPop((char *) &s_sms,iCycle); // неразрушающее чтение

		if(s_sms.iVersion != SMS_VERSION)
		{
			bWorkNeed = true;
			break;
		}
	}
	if (bWorkNeed)
	{
		// здесь производится проверка версии записи и при необходимости
		// upgrade (инициализация дополнительных полей)
		for(iCycle = 0; iCycle < iQty; iCycle++)
		{
			sms->fnPop((char *) &s_sms); // разрушающее чтение

			if(s_sms.iVersion != SMS_VERSION)
			{
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wimplicit-fallthrough"
				switch(s_sms.iVersion)
				{
					case 1:
					s_sms.iGate = 0;
					case 2:
					s_sms.iErrorCode = -1;
					case 3:
					s_sms.bPaused = true;
				}
#pragma GCC diagnostic pop
				s_sms.iVersion = SMS_VERSION;
			}

			// возвращаем запись в кольцо
			sms->fnPush((char *) &s_sms,sizeof(SMS));
		}
	}

	// определяем необходимость удаления устаревших записей
	bWorkNeed = false;
	for(iCycle = 0; iCycle < iQty; iCycle++)
	{
		sms->fnPop((char *) &s_sms,iCycle); // неразрушающее чтение

		if(fnGetDayIndex(s_sms.szDate,szToday) > 28)
		{
			bWorkNeed = true;
			break;
		}
	}
	if (bWorkNeed)
	{
		// удаление устаревших записей
		for(iCycle = 0; iCycle < iQty; iCycle++)
		{
			sms->fnPop((char *) &s_sms); // разрушающее чтение

			// возвращаем запись в кольцо, если не истёк срок её давности
			if(fnGetDayIndex(s_sms.szDate,szToday) <= 28)
				sms->fnPush((char *) &s_sms,sizeof(SMS));
		}
	}
}

// *****************************************************************
// * функции поиска федерального номера сотового телефона в строке *
// *****************************************************************

static int fnGetNumsQty(char *szPhone)
{
	int iQty = 0;

	while (*szPhone)
	{
		switch (*szPhone++)
		{
			case '0':
			case '1':
			case '2':
			case '3':
			case '4':
			case '5':
			case '6':
			case '7':
			case '8':
			case '9':
			iQty++;
		}
	}
	return iQty;
}

static void fnCopyPhone(char *dst,char *szPhone)
{
	*dst = 0;
	while (*szPhone)
	{
		switch (*szPhone++)
		{
			case '9': strcat(dst,"9"); break;
			case '8': strcat(dst,"8"); break;
			case '7': strcat(dst,"7"); break;
			case '6': strcat(dst,"6"); break;
			case '5': strcat(dst,"5"); break;
			case '4': strcat(dst,"4"); break;
			case '3': strcat(dst,"3"); break;
			case '2': strcat(dst,"2"); break;
			case '1': strcat(dst,"1"); break;
			case '0': strcat(dst,"0");
		}
	}
}

static bool fnGetPhoneFederal(char *dst,char *szPhone)
{
	int iNumsQty = fnGetNumsQty(szPhone);

	switch (iNumsQty)
	{
		case 10: fnCopyPhone(dst +1,szPhone); break;
		case 11: fnCopyPhone(dst,szPhone);
	}
	switch (iNumsQty)
	{
		case 10:
		case 11:
		*dst = '7';
		if (*(dst +1) != '9') // ошибка: номера сотовых операторов должны начинаться с девятки
			return false;
		return true;

		default: // не федеральный номер
		*dst = 0;
		return false;
	}
}

// копирует в dst найденный в szPhones номер сотового телефона в формате 71234567890
bool fnGetPhone(char *dst,char *szPhones)
{
#if defined(__WATCOMC__)
	std::auto_ptr<RING> lexems (new RING("bool fnGetPhone(char *,char *): lexems",INI_MAX_LINE_LEN));
#else
	std::unique_ptr<RING> lexems (new RING("bool fnGetPhone(char *,char *): lexems",INI_MAX_LINE_LEN));
#endif // defined
	char sz [REG_NAME_LEN +1];

	strcpy(sz,szPhones);
	fnCharToComma(sz,'.'); // заменяем точки на запятые
	// размещаем список телефонов в массив
	fnGetLexems(lexems.get(),sz,INI_EXCLUDE_COMMA);
	while (lexems->fnGetQty())
	{
		// проверяем каждый телефонный номер
		lexems->fnPop(sz);
		if (fnGetPhoneFederal(dst,sz))
			return true;
	}
	return false; // федеральных номеров не найдено
}

// ***************************************************************
// * функции загрузки новых sms-сообщений в очередь для отправки *
// ***************************************************************

static int fnGetNextId(void)
{
	int iQty;
	int iCycle;
	SMS s_sms;
	int iNextId = 0;

	iQty = sms->fnGetQty();
	for (iCycle = 0; iCycle < iQty; iCycle++)
	{
		sms->fnPop((char *) &s_sms,iCycle);
		if (s_sms.iId >= iNextId)
			iNextId = s_sms.iId +1;
	}
	return iNextId;
}

// тест
void fnSendSmsTest(QUERY *query,char *szFederalPhone,char *szText)
{
	SMS s_sms;
	SYSTEMTIME lt;

	// инициализация
	memset(&s_sms,0,sizeof (SMS));
	s_sms.iVersion = SMS_VERSION;
	s_sms.iId = fnGetNextId();
	s_sms.iGate = 0;
	s_sms.iType = 0; // тестовое SMS-сообщение
	if (config.bSmsCreatePausedType0)
		s_sms.bPaused = true;
	s_sms.iStep = 0;
	// время создания записи
	GetLocalTime(&lt);
	sprintf(s_sms.szDate,"%02u/%02u/%4u",lt.wDay,lt.wMonth,lt.wYear);
	sprintf(s_sms.szTime,"%02u:%02u",lt.wHour,lt.wMinute);
	SystemTimeToFileTime(&lt,&s_sms.ft_create);

	strcpy(s_sms.szPrefix,query->szPrefix);
	strcpy(s_sms.szPhone,szFederalPhone);
	strcpy(s_sms.szText,szText);

	// загрузка в очередь отправки
	sms->fnPush((char *) &s_sms,sizeof (SMS));
}

// выдача результатов
void fnSendSmsGivingResults(QUERY *query,int iVisitNum)
{
	int iVisit;
	int iClientNum;
	int iClient;
	SMS s_sms;
	SYSTEMTIME lt;
	char szFederalPhone [11 +1];
	string s;
	char szTmp [20];

	iVisit = fnVisitSearchByNum(iVisitNum);
	if (iVisit == -1)
		return;
	iClientNum = visit [iVisit]->fnGetClientNum();
	iClient = fnClientSearchByNum(iClientNum);
	if (iClient == -1)
		return;

	// не отправлять SMS, если есть адрес электронной почты клиента
	if (config.bSmsNotSendResultsIfEmailExist
		&& strstr(client [iClient]->fnGetEmail(),"@"))
		return;
	if (client [iClient]->fnGetBanOnSMS())
		return;
	if (! client [iClient]->fnGetConsentToReceiveSMS())
		return;

	// инициализация
	memset(&s_sms,0,sizeof (SMS));
	s_sms.iVersion = SMS_VERSION;
	s_sms.iId = fnGetNextId();
	s_sms.iGate = 0;
	s_sms.iType = 1; // SMS-уведомление о готовности результатов
	if (config.bSmsCreatePausedType1)
		s_sms.bPaused = true;
	s_sms.iStep = 0;
	// время создания записи
	GetLocalTime(&lt);
	sprintf(s_sms.szDate,"%02u/%02u/%4u",lt.wDay,lt.wMonth,lt.wYear);
	sprintf(s_sms.szTime,"%02u:%02u",lt.wHour,lt.wMinute);
	SystemTimeToFileTime(&lt,&s_sms.ft_create);

	strcpy(s_sms.szPrefix,query->szPrefix);
	// сотовый телефон
	if (fnGetPhone(szFederalPhone,client [iClient]->fnGetPhoneCell()))
		strcpy(s_sms.szPhone,szFederalPhone);
	else
		return;
	// номер и дата оформления документа
	s.clear();
	// префикс используется и присутствует
	if (config.bUseContractPrefix && strlen(visit [iVisit]->fnGetPrefix()))
	{
		s += visit [iVisit]->fnGetPrefix();
		s += "-";
	}
	sprintf(szTmp,"%i",visit [iVisit]->fnGetContractNum());
	s += szTmp;
	s += " от ";
	s += visit [iVisit]->fnGetContractDate();
	// текст сообщения (подставляем номер и дату оформления документа)
	fnReplace(s_sms.szText,sizeof (s_sms.szText) -1, // dst_len
		config.szSmsGivingResults,"{contract}",s.c_str());

	s_sms.iClientNum = visit [iVisit]->fnGetClientNum(); // клиент
	s_sms.iContractNum = visit [iVisit]->fnGetContractNum();
	strcpy(s_sms.szContractDate,visit [iVisit]->fnGetContractDate());

	// загрузка в очередь отправки
	sms->fnPush((char *) &s_sms,sizeof (SMS));
}

// поздравление с днём рождения
bool fnSendSmsBirthday(QUERY *query,int iClientNum)
{
	int iClient = fnClientSearchByNum(iClientNum);
	SMS s_sms;
	SYSTEMTIME lt;
	char szFederalPhone [11 +1];

	if (iClient == -1)
		return false;

	// инициализация
	memset(&s_sms,0,sizeof (SMS));
	s_sms.iVersion = SMS_VERSION;
	s_sms.iId = fnGetNextId();
	s_sms.iGate = 0;
	s_sms.iType = 2; // поздравление с днём рождения
	if (config.bSmsCreatePausedType2)
		s_sms.bPaused = true;
	s_sms.iStep = 0;
	// время создания записи
	GetLocalTime(&lt);
	sprintf(s_sms.szDate,"%02u/%02u/%4u",lt.wDay,lt.wMonth,lt.wYear);
	sprintf(s_sms.szTime,"%02u:%02u",lt.wHour,lt.wMinute);
	SystemTimeToFileTime(&lt,&s_sms.ft_create);

	strcpy(s_sms.szPrefix,query->szPrefix);
	// сотовый телефон
	if (fnGetPhone(szFederalPhone,client [iClient]->fnGetPhoneCell()))
		strcpy(s_sms.szPhone,szFederalPhone);
	else
		return false;
	// текст сообщения
	strcpy(s_sms.szText,config.szSmsBirthday);
	s_sms.iClientNum = iClientNum; // клиент

	// загрузка в очередь отправки
	sms->fnPush((char *) &s_sms,sizeof (SMS));
	return true;
}

// информация клиентам
bool fnSendSmsInfo(QUERY *query,int iClientNum,char *szText)
{
	int iClient = fnClientSearchByNum(iClientNum);
	SMS s_sms;
	SYSTEMTIME lt;
	char szFederalPhone [11 +1];

	if (iClient == -1)
		return false;

	// инициализация
	memset(&s_sms,0,sizeof (SMS));
	s_sms.iVersion = SMS_VERSION;
	s_sms.iId = fnGetNextId();
	s_sms.iGate = 0;
	s_sms.iType = 3; // рассылка информации клиентам
	if (config.bSmsCreatePausedType3)
		s_sms.bPaused = true;
	s_sms.iStep = 0;
	// время создания записи
	GetLocalTime(&lt);
	sprintf(s_sms.szDate,"%02u/%02u/%4u",lt.wDay,lt.wMonth,lt.wYear);
	sprintf(s_sms.szTime,"%02u:%02u",lt.wHour,lt.wMinute);
	SystemTimeToFileTime(&lt,&s_sms.ft_create);

	strcpy(s_sms.szPrefix,query->szPrefix);
	// сотовый телефон
	if (fnGetPhone(szFederalPhone,client [iClient]->fnGetPhoneCell()))
		strcpy(s_sms.szPhone,szFederalPhone);
	else
		return false;
	// текст сообщения
	strcpy(s_sms.szText,szText);
	s_sms.iClientNum = iClientNum; // клиент

	// загрузка в очередь отправки
	sms->fnPush((char *) &s_sms,sizeof (SMS));
	return true;
}

// *****************************************
// * функции отправки сообщений из очереди *
// *****************************************

// проверка получения ответа сервера
static bool fnCheckForServerResponse(char *chBuf,unsigned int uiLen)
{
	char *chCopyBuf = new char [uiLen +1];
	VDL_TEXT *answer = new VDL_TEXT;
	int iLinesQty;
	bool bReceipt = false;
	int iCycle;
	int iContentLenMode = 0;
	int iContentLen = 0;
	int iLineLen;
	int iHttpLen = 0;
	bool bHttpLenDefined = false;
	int iEmptyLineNum = 0;
	char szTmp [20];
	unsigned int uiChunkLen;

	memcpy(chCopyBuf,chBuf,uiLen);
	*(chCopyBuf+uiLen) = 0;
	answer->fnReceive(chCopyBuf,uiLen);
	delete [] chCopyBuf;

	iLinesQty = answer->fnGetLinesQty();
	if (iLinesQty < 3)
	{
		delete answer;
		return bReceipt;
	}

	// определяем способ передачи размера посылки
	for (iCycle = 0; iCycle < iLinesQty; iCycle++)
	{
		if (! strnicmp(answer->fnGetLine(iCycle),"Content-Length:",15))
		{
			iContentLenMode = 1; // с использованием "Content-Length" в заголовке
			iContentLen = atoi(answer->fnGetLine(iCycle) +15);
			break;
		}
		if (! strcmpi(answer->fnGetLine(iCycle),"Transfer-Encoding: chunked"))
		{
			iContentLenMode = 2; // по размерам фрагментов
			break;
		}
	}
	if (! iContentLenMode)
	{
		delete answer;
		return bReceipt;
	}

	// подсчитываем размер заголовка, сохраняем номер пустой строки после заголовка
	for (iCycle = 0; iCycle < iLinesQty; iCycle++)
	{
		iLineLen = answer->fnGetLineLen(iCycle);
		iHttpLen += iLineLen +2;
		if (! iLineLen) // заголовок принят полностью
		{
			bHttpLenDefined = true;
			iEmptyLineNum = iCycle;
			break;
		}
	}
	if (! bHttpLenDefined)
	{
		delete answer;
		return bReceipt;
	}

	switch (iContentLenMode) // способ передачи размера посылки
	{
		case 1: // с использованием "Content-Length" в заголовке
		if ((unsigned int) (iHttpLen+iContentLen) == uiLen)
			bReceipt = true;
		break;

		case 2: // по размерам фрагментов
		for (iCycle = iEmptyLineNum +1; iCycle < iLinesQty; )
		{
			// длина фрагмента
			strcpy(szTmp,"0x");
			strcat(szTmp,answer->fnGetLine(iCycle));
			sscanf(szTmp,"%x",&uiChunkLen);
			iCycle++;
			if (iCycle >= iLinesQty)
				break;

			if (uiChunkLen == 0) // фрагмент нулевой длины
				if (iCycle < iLinesQty) // строки не закончились
					if (answer->fnGetLineLen(iCycle +1) == 0) // следующая строка пуста
					{
						bReceipt = true;
						break;
					}

			if (answer->fnGetLineLen(iCycle) != uiChunkLen) // неполный фрагмент
				break;
			iCycle++;
		}
		break;
	}

	delete answer;
	return bReceipt;
}

// функция преобразования взята из интернета
static string cp1251_to_utf8(const char *str)
{
	static string res;
	int result_u,result_c;

	result_u = MultiByteToWideChar(1251,
		0,
		str,
		-1,
		0,
		0);

	if (! result_u)
		return 0;

	wchar_t *ures = new wchar_t [result_u];

	if (! MultiByteToWideChar(1251,
		0,
		str,
		-1,
		ures,
		result_u))
	{
		delete [] ures;
		return 0;
	}

	result_c = WideCharToMultiByte(
		CP_UTF8,
		0,
		ures,
		-1,
		0,
		0,
		0,
		0);

	if (! result_c)
	{
		delete [] ures;
		return 0;
	}

	char *cres = new char [result_c];

	if (! WideCharToMultiByte(
		CP_UTF8,
		0,
		ures,
		-1,
		cres,
		result_c,
		0,
		0))
	{
		delete [] ures;
		delete [] cres;
		return 0;
	}
	delete [] ures;
	res.clear();
	res.append(cres);
	delete [] cres;
	return res;
}

static string utf8_to_cp1251(const char *str)
{
	static string res;
	int result_u,result_c;

	result_u = MultiByteToWideChar(
		CP_UTF8,
		0,
		str,
		-1,
		0,
		0);

	if (! result_u)
		return 0;

	wchar_t *ures = new wchar_t [result_u];

	if(! MultiByteToWideChar(
		CP_UTF8,
		0,
		str,
		-1,
		ures,
		result_u))
	{
		delete[] ures;
		return 0;
	}

	result_c = WideCharToMultiByte(
		1251,
		0,
		ures,
		-1,
		0,
		0,
		0,
		0);

	if(! result_c)
	{
		delete [] ures;
		return 0;
	}

	char *cres = new char[result_c];

	if(! WideCharToMultiByte(
		1251,
		0,
		ures,
		-1,
		cres,
		result_c,
		0,
		0))
	{
		delete[] cres;
		return 0;
	}
	delete[] ures;
	res.clear();
	res.append(cres);
	delete[] cres;
	return res;
}

// ****************************************************************************
// *                                                                          *
// *                               1000sms.ru                                 *
// *                                                                          *
// ****************************************************************************

void fnSmsQueueExecuteSend_1000sms(SMS *s_sms)
{
	SOCKET client_socket;
	string s;
	char szTmp [20];
	char *szInfo;
	sockaddr_in dest_addr;
	hostent *d_addr = NULL;
	int iResult;
	VDL_TEXT *query;
	char szUser [64+3 +1];
	string utf8;
	int iCycle;
	char szSenderName [20 +1];
	char *chBuf;
	unsigned int uiLen;
	VDL_TEXT *in_text;
	SYSTEMTIME lt;
	RING *lexems;
	char sz [XML_MAX_LEXEM_LEN +1];

#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wimplicit-fallthrough"
	switch (s_sms->iStep)
	{
		// **********************
		// * передача сообщения *
		// **********************
		case 0:
		// создаём сокет
		client_socket = socket(AF_INET,SOCK_STREAM,0);
		if (client_socket == 0 || client_socket == INVALID_SOCKET)
		{
			s = "fn=fnSmsQueueExecuteSend_1000sms() code=socket1 msg='socket error' error=";
			itoa(WSAGetLastError(),szTmp,10);
			s += szTmp;
			szInfo = new char [s.length() +1];
			strcpy(szInfo,s.c_str());
			throw szInfo;
		}
		// заполнение структуры sockaddr_in
		dest_addr.sin_family = AF_INET;
		dest_addr.sin_port = htons((unsigned short) 80);
		LeaveCriticalSection(&app_cs);
		d_addr = gethostbyname("api.1000sms.ru");
		EnterCriticalSection(&app_cs);
		if (d_addr == NULL)
		{
			closesocket(client_socket);

			s = "fn=fnSmsQueueExecuteSend_1000sms() code=connect0 msg='host not found'";
			szInfo = new char [s.length() +1];
			strcpy(szInfo,s.c_str());
			throw szInfo;
		}
		dest_addr.sin_addr.s_addr = *(DWORD* ) d_addr->h_addr_list [0];

		LeaveCriticalSection(&app_cs);
		iResult = connect(client_socket,(sockaddr *) &dest_addr,sizeof (dest_addr));
		EnterCriticalSection(&app_cs);
		if (iResult)
		{
			closesocket(client_socket);

			s = "fn=fnSmsQueueExecuteSend_1000sms() code=connect1 msg='connect failed' error=";
			itoa(WSAGetLastError(),szTmp,10);
			s += szTmp;
			szInfo = new char [s.length() +1];
			strcpy(szInfo,s.c_str());
			throw szInfo;
		}

		// счётчик попыток
		if (config.bSmsBanEnabled)
		{
			config.iSmsCurrentTryQty++;
			bConfigUpdate = true;
			if (config.iSmsCurrentTryQty > config.iSmsMaxTryQty)
			{
				config.bSmsEnabled = false;
				config.iSmsCurrentTryQty = 0;

				closesocket(client_socket);

				s = "fn=fnSmsQueueExecuteSend_1000sms() code=ban msg='max.try counter, SMS disabled'";
				szInfo = new char [s.length() +1];
				strcpy(szInfo,s.c_str());
				throw szInfo;
			}
		}

		// запрос
		query = new VDL_TEXT;
		s = "GET /?method=push_msg&email=";
		fnReplace(szUser,sizeof (szUser) -1,config.szSms1000Email,"@","%40");
		s += szUser;
		s += "&password=";
		s += config.szSms1000Password;
		// текст сообщения в кодировке utf8
		s += "&text=";
		utf8 = cp1251_to_utf8(s_sms->szText);
		for (iCycle = 0; iCycle < (int) utf8.length(); iCycle++)
		{
			s += "%";
			sprintf(szTmp,"%02x",(unsigned char) utf8 [iCycle]);
			s += szTmp;
		}
		s += "&phone=";
		s += s_sms->szPhone;
		// подпись отправителя (регистрируется на сайте)
		s += "&sender_name=";
		fnReplace(szSenderName,sizeof (szSenderName) -1,config.szSms1000SenderName," ","%20");
		s += szSenderName;
		s += " HTTP/1.1";
		query->fnAddLine(s.c_str());
		query->fnAddLine("User-Agent: fnSmsQueueExecuteSend_1000sms()");
		query->fnAddLine("Host: api.1000sms.ru");
		query->fnAddLine("");
		chBuf = new char [WS_MAX_SEND_SIZE +1]; // +1 для завершающего нуля
		uiLen = query->fnSend(chBuf,WS_MAX_SEND_SIZE);
		delete query;

		// передача
		LeaveCriticalSection(&app_cs);
		iResult = send(client_socket,chBuf,uiLen,0);
		EnterCriticalSection(&app_cs);
		if (iResult == SOCKET_ERROR)
		{
			closesocket(client_socket);
			delete [] chBuf;

			s = "fn=fnSmsQueueExecuteSend_1000sms() code=send msg='send error' error=";
			itoa(WSAGetLastError(),szTmp,10);
			s += szTmp;
			szInfo = new char [s.length() +1];
			strcpy(szInfo,s.c_str());
			throw szInfo;
		}
		// сохраняем в лог-файл
		*(chBuf+uiLen) = 0;
		fnLogSms(chBuf);

		// дождаться ответа
		LeaveCriticalSection(&app_cs);
		uiLen = 0;
		do
		{
			iResult = recv(client_socket,chBuf+uiLen,WS_MAX_SEND_SIZE-uiLen,0);
			if (iResult == 0 || iResult == SOCKET_ERROR)
				break;
			uiLen += iResult;
		} while (! fnCheckForServerResponse(chBuf,uiLen));
		EnterCriticalSection(&app_cs);
		closesocket(client_socket);
		// сохраняем в лог-файл
		*(chBuf+uiLen) = 0;
		fnLogSms(chBuf);
		if (iResult == SOCKET_ERROR)
		{
			delete [] chBuf;
			return;
		}

		// контроль статуса HTTP
		in_text = new VDL_TEXT;
		in_text->fnReceive(chBuf,uiLen);
		// в объекте in_text сейчас содержится полный текст http-ответа, например:
/*
HTTP/1.1 200 OK
Server: nginx
Date: Fri, 22 Mar 2019 09:30:07 GMT
Content-Type: application/xml
Access-Control-Allow-Origin: *
X-Cache: MISS from DNKLAB-ROUTER.workgroup
X-Cache-Lookup: MISS from DNKLAB-ROUTER.workgroup:8080
Transfer-Encoding: chunked
Via: ICAP/1.0 DNKLAB-ROUTER.workgroup (C-ICAP/0.4.2 Antivirus service ), 1.1 DNKLAB-ROUTER.workgroup (squid/3.5.27)
Connection: keep-alive

F0
<?xml version="1.0" encoding="UTF-8"?>
<response><msg><err_code>0</err_code><text>OK</text><type>message</type></msg>
<data><id>47685656</id><credits>2.29</credits><n_raw_sms>1</n_raw_sms>
<sender_name>rekvizit</sender_name></data></response>
0
*/
		if (strcmpi(in_text->fnGetLine(0),"HTTP/1.1 200 OK"))
		{
			s_sms->iGate = SMS_GATE_1000; // приписываем к шлюзу
			s_sms->bAction = true;
			s_sms->bCompleted = true;
			s_sms->bSuccess = false; // неудача
			GetLocalTime(&lt);
			sprintf(s_sms->szStepTime,"%02u:%02u",lt.wHour,lt.wMinute);
			delete [] chBuf;
			delete in_text;
			return;
		}

		lexems =  new RING("fnSmsQueueExecuteSend_1000sms: lexems",1024);
		lexems->fnLoadXML(in_text);
		delete in_text;
		for (iCycle = 0; iCycle < lexems->fnGetQty(); iCycle++)
		{
			lexems->fnPop(sz,iCycle);

			if (! strcmpi(sz,"<err_code>"))
			{
				lexems->fnPop(sz,iCycle +1);
				s_sms->iErrorCode = atoi(sz);
				continue;
			}
			if (! strcmpi(sz,"<id>"))
			{
				lexems->fnPop(sz,iCycle +1);
				s_sms->iSmsNum = atoi(sz);
				continue;
			}
		}
		delete lexems;

		// обновление записи
		s_sms->iGate = SMS_GATE_1000; // приписываем к шлюзу
		s_sms->iStep = 1;
		s_sms->bAction = true;
		GetLocalTime(&lt);
		sprintf(s_sms->szStepTime,"%02u:%02u",lt.wHour,lt.wMinute);

		// *******************
		// * контроль шага 0 *
		// *******************
		case 1:
		// сообщение принято сервисом
		if (s_sms->iErrorCode == 0)
		{
			// обновление записи
			s_sms->iStep = 2;
			GetLocalTime(&lt);
			sprintf(s_sms->szStepTime,"%02u:%02u",lt.wHour,lt.wMinute);
			// счётчик попыток
			if (config.bSmsBanEnabled)
			{
				config.iSmsCurrentTryQty = 0;
				bConfigUpdate = true;
			}
		}
		else
		{
			s_sms->bCompleted = true;
			s_sms->bSuccess = false;
		}
		break;

		// **********************************************
		// * проверка состояния отправленного сообщения *
		// **********************************************
		case 2:
		// создаём сокет
		client_socket = socket(AF_INET,SOCK_STREAM,0);
		if (client_socket == 0 || client_socket == INVALID_SOCKET)
		{
			s = "fn=fnSmsQueueExecuteSend_1000sms() code=socket1 msg='socket error' error=";
			itoa(WSAGetLastError(),szTmp,10);
			s += szTmp;
			szInfo = new char [s.length() +1];
			strcpy(szInfo,s.c_str());
			throw szInfo;
		}
		// заполнение структуры sockaddr_in
		dest_addr.sin_family = AF_INET;
		dest_addr.sin_port = htons((unsigned short) 80);
		LeaveCriticalSection(&app_cs);
		d_addr = gethostbyname("api.1000sms.ru");
		EnterCriticalSection(&app_cs);
		if (d_addr == NULL)
		{
			closesocket(client_socket);

			s = "fn=fnSmsQueueExecuteSend_1000sms() code=connect0 msg='host not found'";
			szInfo = new char [s.length() +1];
			strcpy(szInfo,s.c_str());
			throw szInfo;
		}
		dest_addr.sin_addr.s_addr = *(DWORD* ) d_addr->h_addr_list [0];

		LeaveCriticalSection(&app_cs);
		iResult = connect(client_socket,(sockaddr *) &dest_addr,sizeof (dest_addr));
		EnterCriticalSection(&app_cs);
		if (iResult)
		{
			closesocket(client_socket);

			s = "fn=fnSmsQueueExecuteSend_1000sms() code=connect1 msg='connect failed' error=";
			itoa(WSAGetLastError(),szTmp,10);
			s += szTmp;
			szInfo = new char [s.length() +1];
			strcpy(szInfo,s.c_str());
			throw szInfo;
		}

		// запрос
		query = new VDL_TEXT;
		s = "GET /?method=get_msg_report&email=";
		fnReplace(szUser,sizeof (szUser) -1,config.szSms1000Email,"@","%40");
		s += szUser;
		s += "&password=";
		s += config.szSms1000Password;
		s += "&id=";
		sprintf(szTmp,"%i",s_sms->iSmsNum);
		s += szTmp;
		s += " HTTP/1.1";
		query->fnAddLine(s.c_str());
		query->fnAddLine("User-Agent: fnSmsQueueExecuteSend_1000sms()");
		query->fnAddLine("Host: api.1000sms.ru");
		query->fnAddLine("");
		chBuf = new char [WS_MAX_SEND_SIZE +1]; // +1 для завершающего нуля
		uiLen = query->fnSend(chBuf,WS_MAX_SEND_SIZE);
		delete query;

		// передача
		LeaveCriticalSection(&app_cs);
		iResult = send(client_socket,chBuf,uiLen,0);
		EnterCriticalSection(&app_cs);
		if (iResult == SOCKET_ERROR)
		{
			closesocket(client_socket);
			delete [] chBuf;

			s = "fn=fnSmsQueueExecuteSend_1000sms() code=send msg='send error' error=";
			itoa(WSAGetLastError(),szTmp,10);
			s += szTmp;
			szInfo = new char [s.length() +1];
			strcpy(szInfo,s.c_str());
			throw szInfo;
		}
		// сохраняем в лог-файл
		*(chBuf+uiLen) = 0;
		fnLogSms(chBuf);

		// дождаться ответа
		LeaveCriticalSection(&app_cs);
		uiLen = 0;
		do
		{
			iResult = recv(client_socket,chBuf+uiLen,WS_MAX_SEND_SIZE-uiLen,0);
			if (iResult == 0 || iResult == SOCKET_ERROR)
				break;
			uiLen += iResult;
		} while (! fnCheckForServerResponse(chBuf,uiLen));
		EnterCriticalSection(&app_cs);
		closesocket(client_socket);
		// сохраняем в лог-файл
		*(chBuf+uiLen) = 0;
		fnLogSms(chBuf);
		if (iResult == SOCKET_ERROR)
		{
			delete [] chBuf;
			return;
		}

		// контроль статуса HTTP
		in_text = new VDL_TEXT;
		in_text->fnReceive(chBuf,uiLen);
		// в объекте in_text сейчас содержится полный текст http-ответа, например:
/*
HTTP/1.1 200 OK
Server: nginx
Date: Fri, 22 Mar 2019 09:34:16 GMT
Content-Type: application/xml
Access-Control-Allow-Origin: *
X-Cache: MISS from DNKLAB-ROUTER.workgroup
X-Cache-Lookup: MISS from DNKLAB-ROUTER.workgroup:8080
Transfer-Encoding: chunked
Via: ICAP/1.0 DNKLAB-ROUTER.workgroup (C-ICAP/0.4.2 Antivirus service ), 1.1 DNKLAB-ROUTER.workgroup (squid/3.5.27)
Connection: keep-alive

22F
<?xml version="1.0" encoding="UTF-8"?>
<response><msg><err_code>0</err_code><text>OK</text><type>message</type></msg>
<data><id>47685656</id><sender_name>rekvizit</sender_name><text>магистра</text>
<phone>79831853847</phone><type>1</type><n_raw_sms>1</n_raw_sms>
<start_time>2019-03-22 12:30:07</start_time><last_update>2019-03-22 12:30:00</last_update>
<dlr_mask></dlr_mask><dlr_url></dlr_url><sms_validity></sms_validity>
<state>1</state><smpp_msgdata></smpp_msgdata><credits>2.290</credits>
<state_text>Доставлено</state_text></data></response>
0
*/
		if (strcmpi(in_text->fnGetLine(0),"HTTP/1.1 200 OK"))
		{
			s_sms->iGate = SMS_GATE_1000; // приписываем к шлюзу
			s_sms->bCompleted = true;
			s_sms->bSuccess = false; // неудача
			GetLocalTime(&lt);
			sprintf(s_sms->szStepTime,"%02u:%02u",lt.wHour,lt.wMinute);
			delete [] chBuf;
			delete in_text;
			return;
		}

		lexems =  new RING("fnSmsQueueExecuteSend_1000sms: lexems",1024);
		lexems->fnLoadXML(in_text);
		delete in_text;
		for (iCycle = 0; iCycle < lexems->fnGetQty(); iCycle++)
		{
			lexems->fnPop(sz,iCycle);

			if (! strcmpi(sz,"<state_text>"))
			{
				lexems->fnPop(sz,iCycle +1);
				s = utf8_to_cp1251(sz);
				strcpy(s_sms->szServiceAnswer,s.c_str());
			}
		}
		delete lexems;

		// обновление записи
		s_sms->iStep = 3;
		GetLocalTime(&lt);
		sprintf(s_sms->szStepTime,"%02u:%02u",lt.wHour,lt.wMinute);

		// *******************
		// * контроль шага 2 *
		// *******************
		case 3:
		do
		{
			if (! strcmp(s_sms->szServiceAnswer,"Доставлено"))
			{
				s_sms->bSuccess = true;
				s_sms->bCompleted = true;
				s_sms->iStep = 4;
				continue;
			}
			if (! strcmp(s_sms->szServiceAnswer,"Не доставлено"))
			{
				s_sms->bSuccess = false;
				s_sms->bCompleted = true;
				s_sms->iStep = 4;
				continue;
			}
			if (! strcmp(s_sms->szServiceAnswer,"Отправлено"))
			{
				s_sms->bPaused = true;
				s_sms->iStep = 2;
				continue;
			}

			s_sms->iStep = 2; // переходим к повторной проверке состояния
		} while (false);

		// обновление записи
		GetLocalTime(&lt);
		sprintf(s_sms->szStepTime,"%02u:%02u",lt.wHour,lt.wMinute);
		break;

	} // end of switch
#pragma GCC diagnostic pop

	// обновление метки времени
	GetLocalTime(&lt);
	SystemTimeToFileTime(&lt,&s_sms->ft_action);
} // end of fnSmsQueueExecuteSend_1000sms()

// ****************************************************************************
// *                                                                          *
// *                               smsaero.ru                                 *
// *                                                                          *
// ****************************************************************************
void fnSmsQueueExecuteSend_smsaero(SMS *s_sms)
{
	SOCKET client_socket;
	sockaddr_in dest_addr;
	VDL_TEXT *query;
	string s;
	md5_context ctx;
	int iCycle;
	char szTmp [20];
	unsigned char digest [16];
	string utf8;
	int iResult;
	char *chBuf;
	unsigned int uiLen;
	VDL_TEXT *in_text;
	SYSTEMTIME lt;
	hostent *d_addr = NULL;
	char szUser [64+3 +1];
	char szSenderName [20 +1];
	char *szInfo;

#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wimplicit-fallthrough"
	switch (s_sms->iStep)
	{
		// **********************
		// * передача сообщения *
		// **********************
		case 0:
		// создаём сокет
		client_socket = socket(AF_INET,SOCK_STREAM,0);
		if (client_socket == 0 || client_socket == INVALID_SOCKET)
		{
			s = "fn=fnSmsQueueExecuteSend_smsaero() code=socket1 msg='socket error' error=";
			itoa(WSAGetLastError(),szTmp,10);
			s += szTmp;
			szInfo = new char [s.length() +1];
			strcpy(szInfo,s.c_str());
			throw szInfo;
		}
		// заполнение структуры sockaddr_in
		dest_addr.sin_family = AF_INET;
		dest_addr.sin_port = htons((unsigned short) 80);
		LeaveCriticalSection(&app_cs);
		d_addr = gethostbyname("gate.smsaero.ru");
		EnterCriticalSection(&app_cs);
		if (d_addr == NULL)
		{
			closesocket(client_socket);

			s = "fn=fnSmsQueueExecuteSend_smsaero() code=connect0 msg='host not found'";
			szInfo = new char [s.length() +1];
			strcpy(szInfo,s.c_str());
			throw szInfo;
		}
		dest_addr.sin_addr.s_addr = *(DWORD* ) d_addr->h_addr_list [0];

		LeaveCriticalSection(&app_cs);
		iResult = connect(client_socket,(sockaddr *) &dest_addr,sizeof (dest_addr));
		EnterCriticalSection(&app_cs);
		if (iResult)
		{
			closesocket(client_socket);

			s = "fnSmsQueueExecuteSend_smsaero() code=connect1 msg='connect failed' error=";
			itoa(WSAGetLastError(),szTmp,10);
			s += szTmp;
			szInfo = new char [s.length() +1];
			strcpy(szInfo,s.c_str());
			throw szInfo;
		}
		// счётчик попыток
		if (config.bSmsBanEnabled)
		{
			config.iSmsCurrentTryQty++;
			bConfigUpdate = true;
			if (config.iSmsCurrentTryQty > config.iSmsMaxTryQty)
			{
				config.bSmsEnabled = false;
				config.iSmsCurrentTryQty = 0;

				closesocket(client_socket);

				s = "fn=fnSmsQueueExecuteSend_smsaero() code=ban msg='max.try counter, SMS disabled'";
				szInfo = new char [s.length() +1];
				strcpy(szInfo,s.c_str());
				throw szInfo;
			}
		}

		// запрос
		query = new VDL_TEXT;
		s = "GET /send/?user=";
		fnReplace(szUser,sizeof (szUser) -1,config.szSmsAeroEmail,"@","%40");
		s += szUser;
		s += "&password=";
		// формируем хэш пароля (md5)
		md5_starts(&ctx);
		md5_update(&ctx,(unsigned char *) config.szSmsAeroPassword,strlen(config.szSmsAeroPassword));
		md5_finish(&ctx,digest);
		for (iCycle = 0; iCycle < 16; iCycle++)
		{
			sprintf(szTmp,"%02x",digest [iCycle]);
			s += szTmp;
		}
		s += "&to=";
		s += s_sms->szPhone;
		s += "&text=";
		// текст сообщения в кодировке utf8
		utf8 = cp1251_to_utf8(s_sms->szText);
		for (iCycle = 0; iCycle < (int) utf8.length(); iCycle++)
		{
			s += "%";
			sprintf(szTmp,"%02x",(unsigned char) utf8 [iCycle]);
			s += szTmp;
		}
		// подпись отправителя (регистрируется на сайте)
		s += "&from=";
		fnReplace(szSenderName,sizeof (szSenderName) -1,config.szSmsAeroSenderName," ","%20");
		s += szSenderName;
		s += " HTTP/1.1";
		query->fnAddLine(s.c_str());
		query->fnAddLine("User-Agent: fnSmsQueueExecuteSend_smsaero()");
		query->fnAddLine("Host: gate.smsaero.ru");
		query->fnAddLine("");
		chBuf = new char [WS_MAX_SEND_SIZE +1]; // +1 для завершающего нуля
		uiLen = query->fnSend(chBuf,WS_MAX_SEND_SIZE);
		delete query;

		// передача
		LeaveCriticalSection(&app_cs);
		iResult = send(client_socket,chBuf,uiLen,0);
		EnterCriticalSection(&app_cs);
		if (iResult == SOCKET_ERROR)
		{
			closesocket(client_socket);
			delete [] chBuf;

			s = "fn=fnSmsQueueExecuteSend_smsaero() code=send msg='send error' error=";
			itoa(WSAGetLastError(),szTmp,10);
			s += szTmp;
			szInfo = new char [s.length() +1];
			strcpy(szInfo,s.c_str());
			throw szInfo;
		}
		// сохраняем в лог-файл
		*(chBuf+uiLen) = 0;
		fnLogSms(chBuf);

		// дождаться ответа
		LeaveCriticalSection(&app_cs);
		uiLen = 0;
		do
		{
			iResult = recv(client_socket,chBuf+uiLen,WS_MAX_SEND_SIZE-uiLen,0);
			if (iResult == 0 || iResult == SOCKET_ERROR)
				break;
			uiLen += iResult;
		} while (! fnCheckForServerResponse(chBuf,uiLen));
		EnterCriticalSection(&app_cs);
		closesocket(client_socket);
		// сохраняем в лог-файл
		*(chBuf+uiLen) = 0;
		fnLogSms(chBuf);
		if (iResult == SOCKET_ERROR)
		{
			delete [] chBuf;
			return;
		}

		// копируем ответ сервиса
		in_text = new VDL_TEXT;
		in_text->fnReceive(chBuf,uiLen);
		// контроль статуса HTTP
		if (strcmpi(in_text->fnGetLine(0),"HTTP/1.1 200 OK"))
		{
			s_sms->iGate = SMS_GATE_AERO; // приписываем к шлюзу
			s_sms->bAction = true;
			s_sms->bCompleted = true;
			s_sms->bSuccess = false;
			GetLocalTime (&lt);
			sprintf(s_sms->szStepTime,"%02u:%02u",lt.wHour,lt.wMinute);
			delete [] chBuf;
			delete in_text;
			return;
		}
		// контроль длины ответа сервера
		if (in_text->fnGetLineLen(in_text->fnGetLinesQty() -1) > 64)
		{
			s_sms->iGate = SMS_GATE_AERO; // приписываем к шлюзу
			s_sms->bAction = true;
			s_sms->bCompleted = true;
			s_sms->bSuccess = false;
			GetLocalTime(&lt);
			sprintf(s_sms->szStepTime,"%02u:%02u",lt.wHour,lt.wMinute);
			delete [] chBuf;
			delete in_text;
			return;
		}
		delete [] chBuf;
		strcpy(s_sms->szServiceAnswer,in_text->fnGetLine(in_text->fnGetLinesQty() -1));
		delete in_text;

		// обновление записи
		s_sms->iGate = SMS_GATE_AERO; // приписываем к шлюзу
		s_sms->iStep = 1;
		s_sms->bAction = true;
		GetLocalTime(&lt);
		sprintf(s_sms->szStepTime,"%02u:%02u",lt.wHour,lt.wMinute);

		// *******************
		// * контроль шага 1 *
		// *******************
		case 1:
		// сообщение принято сервисом ("2664302=accepted")
		if (strstr(s_sms->szServiceAnswer,"accepted"))
		{
			s_sms->iSmsNum = atoi(s_sms->szServiceAnswer);
			// обновление записи
			s_sms->iStep = 2;
			GetLocalTime(&lt);
			sprintf(s_sms->szStepTime,"%02u:%02u",lt.wHour,lt.wMinute);
			// счётчик попыток
			if (config.bSmsBanEnabled)
			{
				config.iSmsCurrentTryQty = 0;
				bConfigUpdate = true;
			}
			break;
		}
		// не все обязательные поля заполнены ("empty field. reject.")
		if (strstr(s_sms->szServiceAnswer,"empty"))
		{
			s_sms->bCompleted = true;
			s_sms->bSuccess = false;
			break;
		}
		// ошибка авторизации ("incorrect user or password. reject")
		if (strstr(s_sms->szServiceAnswer,"password"))
		{
			s_sms->bError = true;
			s_sms->iStep = 0; // предыдущий шаг
			GetLocalTime(&lt);
			sprintf(s_sms->szStepTime,"%02u:%02u",lt.wHour,lt.wMinute);
			break;
		}
		// недостаточно sms на балансе ("no credits")
		if (strstr(s_sms->szServiceAnswer,"credits"))
		{
			s_sms->bError = true;
			s_sms->iStep = 0; // предыдущий шаг
			GetLocalTime(&lt);
			sprintf(s_sms->szStepTime,"%02u:%02u",lt.wHour,lt.wMinute);
			break;
		}
		// неверная (незарегистрированная) подпись отправителя
		// ("incorrect sender name. reject")
		if (strstr(s_sms->szServiceAnswer,"sender"))
		{
			s_sms->bError = true;
			s_sms->iStep = 0; // предыдущий шаг
			GetLocalTime(&lt);
			sprintf(s_sms->szStepTime,"%02u:%02u",lt.wHour,lt.wMinute);
			break;
		}
		// неверно задан номер телефона (формат 71234567890)
		// ("incorrect destination adress. reject")
		if (strstr(s_sms->szServiceAnswer,"destination"))
		{
			s_sms->bCompleted = true;
			s_sms->bSuccess = false;
			break;
		}
		// неправильный формат даты ("incorrect date. reject")
		if (strstr(s_sms->szServiceAnswer,"date"))
		{
			s_sms->bCompleted = true;
			s_sms->bSuccess = false;
			break;
		}
		break;

		// **********************************************
		// * проверка состояния отправленного сообщения *
		// **********************************************
		case 2:
		// создаём сокет
		client_socket = socket(AF_INET,SOCK_STREAM,0);
		if (client_socket == 0 || client_socket == INVALID_SOCKET)
		{
			s = "fn=fnSmsQueueExecuteSend_smsaero() code=socket1 msg='socket error' error=";
			itoa (WSAGetLastError(),szTmp,10);
			s += szTmp;
			szInfo = new char [s.length() +1];
			strcpy(szInfo,s.c_str());
			throw szInfo;
		}
		// заполнение структуры sockaddr_in
		dest_addr.sin_family = AF_INET;
		dest_addr.sin_port = htons((unsigned short) 80);
		LeaveCriticalSection(&app_cs);
		d_addr = gethostbyname("gate.smsaero.ru");
		EnterCriticalSection(&app_cs);
		if (d_addr == NULL)
		{
			closesocket(client_socket);

			s = "fn=fnSmsQueueExecuteSend_smsaero() code=connect0 msg='host not found'";
			szInfo = new char [s.length() +1];
			strcpy(szInfo,s.c_str());
			throw szInfo;
		}
		dest_addr.sin_addr.s_addr = *(DWORD* ) d_addr->h_addr_list [0];

		LeaveCriticalSection(&app_cs);
		iResult = connect(client_socket,(sockaddr *) &dest_addr,sizeof (dest_addr));
		EnterCriticalSection(&app_cs);
		if (iResult)
		{
			closesocket(client_socket);

			s = "fn=fnSmsQueueExecuteSend_smsaero() code=connect1 msg='connect failed' error=";
			itoa(WSAGetLastError(),szTmp,10);
			s += szTmp;
			szInfo = new char [s.length() +1];
			strcpy (szInfo,s.c_str());
			throw szInfo;
		}

		// запрос
		query = new VDL_TEXT;
		s = "GET /status/?user=";
		fnReplace(szUser,sizeof (szUser) -1,config.szSmsAeroEmail,"@","%40");
		s += szUser;
		s += "&password=";
		// формируем хэш пароля (md5)
		md5_starts(&ctx);
		md5_update(&ctx,(unsigned char *) config.szSmsAeroPassword,strlen(config.szSmsAeroPassword));
		md5_finish(&ctx,digest);
		for (iCycle = 0; iCycle < 16; iCycle++)
		{
			sprintf(szTmp,"%02x",digest [iCycle]);
			s += szTmp;
		}
		s += "&id=";
		sprintf(szTmp,"%u",s_sms->iSmsNum);
		s += szTmp;
		s += " HTTP/1.1";
		query->fnAddLine(s.c_str ());
		query->fnAddLine("User-Agent: fnSmsQueueExecuteSend_smsaero()");
		query->fnAddLine("Host: gate.smsaero.ru");
		query->fnAddLine("");
		chBuf = new char [WS_MAX_SEND_SIZE +1]; // +1 для завершающего нуля
		uiLen = query->fnSend(chBuf,WS_MAX_SEND_SIZE);
		delete query;

		// передача
		LeaveCriticalSection(&app_cs);
		iResult = send(client_socket,chBuf,uiLen,0);
		EnterCriticalSection(&app_cs);
		if (iResult == SOCKET_ERROR)
		{
			closesocket(client_socket);
			delete [] chBuf;

			s = "fn=fnSmsQueueExecuteSend_smsaero() code=send msg='send error' error=";
			itoa(WSAGetLastError(),szTmp,10);
			s += szTmp;
			szInfo = new char [s.length() +1];
			strcpy(szInfo,s.c_str());
			throw szInfo;
		}
		// сохраняем в лог-файл
		*(chBuf+uiLen) = 0;
		fnLogSms(chBuf);

		// дождаться ответа
		LeaveCriticalSection(&app_cs);
		uiLen = 0;
		do
		{
			iResult = recv(client_socket,chBuf+uiLen,WS_MAX_SEND_SIZE-uiLen,0);
			if (iResult == 0 || iResult == SOCKET_ERROR)
				break;
			uiLen += iResult;
		} while (! fnCheckForServerResponse(chBuf,uiLen));
		EnterCriticalSection(&app_cs);
		closesocket(client_socket);
		// сохраняем в лог-файл
		*(chBuf+uiLen) = 0;
		fnLogSms(chBuf);
		if (iResult == SOCKET_ERROR)
		{
			delete [] chBuf;
			return;
		}

		// копируем ответ сервиса
		in_text = new VDL_TEXT;
		in_text->fnReceive(chBuf,uiLen);
		// контроль статуса HTTP
		if (strcmpi(in_text->fnGetLine(0),"HTTP/1.1 200 OK"))
		{
			s_sms->bCompleted = true;
			s_sms->bSuccess = false;
			GetLocalTime(&lt);
			sprintf(s_sms->szStepTime,"%02u:%02u",lt.wHour,lt.wMinute);
			delete [] chBuf;
			delete in_text;
			return;
		}
		// контроль длины ответа сервера
		if (in_text->fnGetLineLen(in_text->fnGetLinesQty () -1) > 64)
		{
			s_sms->bCompleted = true;
			s_sms->bSuccess = false;
			GetLocalTime(&lt);
			sprintf(s_sms->szStepTime,"%02u:%02u",lt.wHour,lt.wMinute);
			delete [] chBuf;
			delete in_text;
			return;
		}
		delete [] chBuf;
		strcpy(s_sms->szServiceAnswer,in_text->fnGetLine(in_text->fnGetLinesQty() -1));
		delete in_text;

		// обновление записи
		s_sms->iStep = 3;
		GetLocalTime(&lt);
		sprintf(s_sms->szStepTime,"%02u:%02u",lt.wHour,lt.wMinute);

		// *******************
		// * контроль шага 2 *
		// *******************
		case 3:
		// сообщение доставлено ("2664302=delivery success")
		if (strstr(s_sms->szServiceAnswer,"success"))
		{
			s_sms->bCompleted = true;
			s_sms->bSuccess = true;
			// обновление записи
			s_sms->iStep = 4;
			GetLocalTime(&lt);
			sprintf(s_sms->szStepTime,"%02u:%02u",lt.wHour,lt.wMinute);
			break;
		}
		// ошибка доставки SMS (абонент в течение времени доставки находился
		// вне зоны действия сети или номер абонента заблокирован)
		// ("2664302=delivery failure")
		if (strstr(s_sms->szServiceAnswer,"failure"))
		{
			s_sms->bCompleted = true;
			s_sms->bSuccess = false;
			// обновление записи
			s_sms->iStep = 4;
			GetLocalTime(&lt);
			sprintf(s_sms->szStepTime,"%02u:%02u",lt.wHour,lt.wMinute);
			break;
		}
		// сообщение доставлено в SMSC ("2664302=smsc submit")
		if (strstr(s_sms->szServiceAnswer,"smsc submit"))
		{
			s_sms->bError = true;
			s_sms->iStep = 2; // предыдущий шаг
			GetLocalTime(&lt);
			sprintf(s_sms->szStepTime,"%02u:%02u",lt.wHour,lt.wMinute);
			break;
		}
		// отвергнуто SMSC ("2664302=smsc reject")
		if (strstr(s_sms->szServiceAnswer,"smsc reject"))
		{
			s_sms->bCompleted = true;
			s_sms->bSuccess = false;
			// обновление записи
			s_sms->iStep = 4;
			GetLocalTime(&lt);
			sprintf(s_sms->szStepTime,"%02u:%02u",lt.wHour,lt.wMinute);
			break;
		}
		// ожидает отправки ("2664302=queue")
		if (strstr(s_sms->szServiceAnswer,"queue"))
		{
			s_sms->bError = true;
			s_sms->iStep = 2; // предыдущий шаг
			GetLocalTime(&lt);
			sprintf(s_sms->szStepTime,"%02u:%02u",lt.wHour,lt.wMinute);
			break;
		}
		// ожидание статуса (запросите позднее) ("2664302=wait status")
		if (strstr(s_sms->szServiceAnswer,"status"))
		{
			s_sms->bError = true;
			s_sms->iStep = 2; // предыдущий шаг
			GetLocalTime(&lt);
			sprintf(s_sms->szStepTime,"%02u:%02u",lt.wHour,lt.wMinute);
			break;
		}
		// неверный идентификатор сообщения ("2664302=incorrect id. reject")
		if (strstr(s_sms->szServiceAnswer,"incorrect id"))
		{
			s_sms->bCompleted = true;
			s_sms->bSuccess = false;
			GetLocalTime(&lt);
			sprintf(s_sms->szStepTime,"%02u:%02u",lt.wHour,lt.wMinute);
			break;
		}
		// не все обязательные поля заполнены ("empty field. reject.")
		if (strstr(s_sms->szServiceAnswer,"empty"))
		{
			s_sms->bCompleted = true;
			s_sms->bSuccess = false;
			GetLocalTime(&lt);
			sprintf(s_sms->szStepTime,"%02u:%02u",lt.wHour,lt.wMinute);
			break;
		}
		// ошибка авторизации ("incorrect user or password. reject")
		if (strstr(s_sms->szServiceAnswer,"password"))
		{
			s_sms->bError = true;
			s_sms->iStep = 2; // предыдущий шаг
			GetLocalTime(&lt);
			sprintf(s_sms->szStepTime,"%02u:%02u",lt.wHour,lt.wMinute);
			break;
		}

	} // end of switch
#pragma GCC diagnostic pop

	// обновление метки времени
	GetLocalTime(&lt);
	SystemTimeToFileTime(&lt,&s_sms->ft_action);
} // end of fnSmsQueueExecuteSend_smsaero()

void fnSmsQueueScan(void)
{
	SYSTEMTIME lt;
	union
	{
		FILETIME ft;
		__int64 time64_current;
	};
	int iQty;
	int iCycle;
	SMS s_sms;
	string s;
	char szToday [11];

	// текущее время
	GetLocalTime(&lt);
	SystemTimeToFileTime(&lt,&ft);

	// текущая дата
	fnGetToday(szToday);

	// сканирование очереди
	iQty = sms->fnGetQty();
	for (iCycle = 0; iCycle < iQty; iCycle++)
	{
		sms->fnPop((char *) &s_sms,iCycle);

		if (s_sms.bCompleted) // обработка завершена
			continue;
		if (s_sms.bAction)
		{
			int iElapsedTime;

			iElapsedTime = (int) ((time64_current-s_sms.time64_action)/10000000);
			if (iElapsedTime < config.iSmsStatusTime*60)
				continue;
			// пауза после ошибки
			if (s_sms.bError
				&& (iElapsedTime < config.iSmsErrorTime*60))
				continue;
		}
		if (s_sms.bPaused)
			continue;
		if (fnGetDayIndex(s_sms.szDate,szToday) > config.iSmsMaxAge)
			continue;

		try
		{
			if (config.bSmsEnabled)
			{
				switch (config.iSmsGate)
				{
					// **************
					// * 1000sms.ru *
					// **************
					case SMS_GATE_1000:
					// SMS-сообщение приписано к другому шлюзу
					if (s_sms.iGate && (s_sms.iGate != SMS_GATE_1000))
						break;
					// вызов соответствующей функции обработки
					fnSmsQueueExecuteSend_1000sms(&s_sms);
					// обновляем запись в очереди
					sms->fnReplace((char *) &s_sms,iCycle,sizeof (SMS));
					break;

					// **************
					// * smsaero.ru *
					// **************
					case SMS_GATE_AERO:
					// SMS-сообщение приписано к другому шлюзу
					if (s_sms.iGate && (s_sms.iGate != SMS_GATE_AERO))
						break;
					// вызов соответствующей функции обработки
					fnSmsQueueExecuteSend_smsaero(&s_sms);
					// обновляем запись в очереди
					sms->fnReplace((char *) &s_sms,iCycle,sizeof (SMS));
					break;

					// другие шлюзы
				}
			}
		}
		catch (char *szExceptionInfo)
		{
			s = "Исключение:\r\n";
			s += szExceptionInfo;
			fnLog(s.c_str());
//			delete [] szExceptionInfo;
		}
		break;
	}
}

// ***********************************************
// * визуальный контроль состояния sms-сообщений *
// ***********************************************

void fnAppSms(QUERY *query)
{
	string s;
	char szTmp [20];
	char szDate1 [11];
	char szToday [11]; // сегодня
	int iQty;
	int iCycle;
	SMS s_sms;
	int iAction;
	int iClient;
	char szClientFIO [100]; // для вывода FIO
	// параметры фильтра - значения по умолчанию
	int iDays = 7;
	int iTuningIconSize = fnGetTuningIconSize(query);

	// загружаем параметры фильтра
	// кол-во дней
	if (login_days->fnGetValue(szTmp,query->szLogin))
		iDays = atoi (szTmp);
	else // параметр не найден
	{
		sprintf(szTmp,"%i",iDays);
		login_days->fnAdd(query->szLogin,szTmp);
	}

	// подготавливаем диапазон дат
	fnGetToday(szToday); // сегодня
	fnCreateDate(szDate1,szToday,-1*(iDays -1));

	fnFormContent(query);
	query->dst->fnAddLine("<p>SMS-сообщения</p>");

	s = "<p class=\"help\">Таблица отображает SMS-сообщения за период, который охватывает \
указанное кол-во дней &mdash; заканчивая сегодняшним днём.</p>";
	query->dst->fnAddLine(s.c_str());

	// выбор периода
	s = "<p><form action=\"/sms_filter_accept\" method=post>";
	s += "Период времени: <select name=days><option";
	if (iDays == 7)
		s += " selected";
	s += " value=\"7\">7 дней<option";
	if (iDays == 14)
		s += " selected";
	s += " value=\"14\">14 дней<option";
	if (iDays == 28)
		s += " selected";
	s += " value=\"28\">28 дней</select>";
	s += "&nbsp;<input type=submit value=\"Применить\"></form></p>";

	// список sms-сообщений
	s += "<table width=\"100%\" border=1><tr>";
	s += "<td>№</td>";
	s += "<td>Дата</td>";
	s += "<td>Время</td>";
	s += "<td>Тип</td>";
	s += "<td>Шаг</td>";
	s += "<td>Префикс</td>";
	s += "<td>Клиент</td>";
	s += "<td>Тел.номер</td>";
	s += "<td>Текст сообщения</td>";
	s += "<td>Ответ сервиса</td>";
	s += "<td>&nbsp;</td>";
	s += "<td>№</td>";
	s += "<td></td></tr>";
	query->dst->fnAddLine(s.c_str());

	// формирование
	iQty = sms->fnGetQty();
	for (iCycle = 0; iCycle < iQty; iCycle++)
	{
		sms->fnPop((char *) &s_sms,iCycle);

		if (! fnCheckDateRange(szDate1,s_sms.szDate,szToday))
			continue;

		s = "<tr";
		// серебряный фон, если запись слишком старая для обработки
		if (fnGetDayIndex(s_sms.szDate,szToday) > config.iSmsMaxAge)
		{
			s += " bgcolor=\"";
			s += szColorSilver;
			s += "\"";
		}
		s += ">";

		// №
		s += "<td valign=top align=right>";
		sprintf(szTmp,"%i",s_sms.iId);
		s += szTmp;
		s += "</td>";

		// дата
		s += "<td valign=top>";
		s += s_sms.szDate;
		s += "</td>";

		// время
		s += "<td valign=top>";
		s += s_sms.szTime;
		s += "</td>";

		// тип
		s += "<td valign=top>";
		switch (s_sms.iType)
		{
			case 0: // тест
			if (iTuningIconSize == 16)
				s += "<img src=\"/images/16x16/appointment-soon.png\" title=\"Тестовое SMS-сообщение\" border=0>";
			else
				s += "<img src=\"/images/24x24/appointment-soon.png\" title=\"Тестовое SMS-сообщение\" border=0>";
			break;

			case 1: // выдача результатов
			if (iTuningIconSize == 16)
				s += "<img src=\"/images/16x16/edit-paste.png\" title=\"Выдача результатов\" border=0>";
			else
				s += "<img src=\"/images/24x24/edit-paste.png\" title=\"Выдача результатов\" border=0>";
			break;

			case 2: // поздравление с днём рождения
			if (iTuningIconSize == 16)
				s += "<img src=\"/images/16x16/face-smile.png\" title=\"Поздравление с днём рождения\" border=0>";
			else
				s += "<img src=\"/images/24x24/face-smile.png\" title=\"Поздравление с днём рождения\" border=0>";
			break;

			case 3: // информация клиентам
			if (iTuningIconSize == 16)
				s += "<img src=\"/images/16x16/attach.png\" title=\"Информация клиентам\" border=0>";
			else
				s += "<img src=\"/images/24x24/attach.png\" title=\"Информация клиентам\" border=0>";
			break;
		}
		s += "</td>";

		// шаг
		s += "<td valign=top><nobr>";
		sprintf(szTmp,"%i",s_sms.iStep);
		s += szTmp;
		if (s_sms.iStep)
		{
			s += " / ";
			s += s_sms.szStepTime;
		}
		s += "</nobr></td>";

		// префикс
		s += "<td valign=top>";
		s += s_sms.szPrefix;
		s += "&nbsp;</td>";

		// клиент
		s += "<td valign=top style=\"font-size: 8pt\">";
		switch (s_sms.iType)
		{
			case 1:
			case 2:
			case 3:
			iClient = fnClientSearchByNum(s_sms.iClientNum);
			if (iClient == -1)
				continue;
			sprintf(szClientFIO,"%s %s %s",
				client [iClient]->fnGetFamily(),
				client [iClient]->fnGetName(),
				client [iClient]->fnGetName2());
			s += szClientFIO;
			break;

			default: s += "&nbsp;";
		}
		s += "</td>";

		// номер телефона получателя
		s += "<td valign=top>";
		switch (s_sms.iType)
		{
			case 0:
			case 1:
			case 2:
			case 3:
			s += s_sms.szPhone;
			break;

			default: s += "&nbsp;";
		}
		s += "</td>";

		// текст сообщения
		s += "<td valign=top style=\"font-size: 8pt\">";
		switch (s_sms.iType)
		{
			case 0:
			case 1:
			case 2:
			case 3:
			s += s_sms.szText;
			break;

			default: s += "&nbsp;";
		}
		s += "</td>";

		// ответ сервиса
		s += "<td valign=top style=\"font-size: 8pt\">";
		if (s_sms.bAction)
		{
			switch (s_sms.iGate)
			{
				case SMS_GATE_1000:
				case SMS_GATE_AERO:
				s += s_sms.szServiceAnswer;
				break;
			}
		}
		s += "</td>";

		// статус
		s += "<td valign=top>";
		if (s_sms.bPaused)
		{
			if (iTuningIconSize == 16)
				s += "<img src=\"/images/16x16/agt_resume.png\" border=0>";
			else
				s += "<img src=\"/images/24x24/agt_resume.png\" border=0>";
		}
		if (s_sms.bCompleted)
		{
			if (s_sms.bSuccess)
			{
				if (iTuningIconSize == 16)
					s += "<img src=\"/images/16x16/agt_action_success.png\" border=0>";
				else
					s += "<img src=\"/images/24x24/agt_action_success.png\" border=0>";
			}
			else
			{
				if (iTuningIconSize == 16)
					s += "<img src=\"/images/16x16/agt_action_fail.png\" border=0>";
				else
					s += "<img src=\"/images/24x24/agt_action_fail.png\" border=0>";
			}
		}
		s += "</td>";

		// №
		s += "<td valign=top align=right>";
		sprintf(szTmp,"%i",s_sms.iId);
		s += szTmp;
		s += "</td>";

		// ссылка на вкладку редактирования
		s += "<td valign=top><a href=\"/sms_edit?sms_id=";
//		sprintf (szTmp,"%i",s_sms.iId);
		s += szTmp;
		s += "\">";
		if (iTuningIconSize == 16)
			s += "<img src=\"/images/16x16/gtk-edit.png\" border=1 title=\"Редактировать\">";
		else
			s += "<img src=\"/images/24x24/gtk-edit.png\" border=1 title=\"Редактировать\">";
		s += "</a></td></tr>";

		query->dst->fnAddLine(s.c_str());
	}
	query->dst->fnAddLine("</table>");

	// *******************
	// * список действий *
	// *******************
	s = "<form action=\"/sms_action_accept\" method=post>";

	s += "<p>Действие: <select name=sms_action>";
	s += "<option value=\"0\">Выберите действие";
	// раздел "Групповые операции обработки"
	s += "<optgroup label=\"Групповые операции обработки\" class=\"no_select\"></optgroup>";
	s += "<option value=\"9\">Поставить на паузу SMS-сообщения с шагом доставки 0";
	s += "<option value=\"1\">Снять паузу с SMS-сообщений типа 0 (тестовые сообщения)";
	s += "<option value=\"2\">Снять паузу с SMS-сообщений типа 1 (сообщения планировщика выдачи результатов)";
	s += "<option value=\"3\">Снять паузу с SMS-сообщений типа 2 (поздравления с днём рождения)";
	s += "<option value=\"4\">Снять паузу с SMS-сообщений типа 3 (информационные сообщения)";
	// раздел "Групповые операции удаления"
	s += "<optgroup label=\"Групповые операции удаления\" class=\"no_select\"></optgroup>";
	s += "<option value=\"5\">Удалить отправленные SMS-сообщения";
	s += "<option value=\"6\">Удалить неотправленные SMS-сообщения";
	s += "<option value=\"10\">Удалить SMS-сообщения типа 0 (тестовые сообщения), с шагом доставки 0";
	s += "<option value=\"11\">Удалить SMS-сообщения типа 1 (сообщения планировщика выдачи результатов), с шагом доставки 0";
	s += "<option value=\"12\">Удалить SMS-сообщения типа 2 (поздравления с днём рождения), с шагом доставки 0";
	s += "<option value=\"13\">Удалить SMS-сообщения типа 3 (информационные сообщения), с шагом доставки 0";
	s += "<option value=\"7\">Удалить SMS-сообщения, обработка которых завершена";
	s += "<option value=\"8\">Удалить все SMS-сообщения";
	s += "</select>";

	// получаем номер транзакции и передаём его в скрытых параметрах
	iAction = actions->fnAdd();
	s += "<input type=hidden name=action value=\"";
	sprintf(szTmp,"%i",iAction);
	s += szTmp;
	s += "\">";
	s += "&nbsp;<input type=submit value=\"Применить\"></p>";

	s += "</form>";
	query->dst->fnAddLine (s.c_str ());

	// ************************************************
	// * таблица с формами для создания SMS-сообщений *
	// ************************************************
	s = "<p>Таблица с формами для создания SMS-сообщений";
	s += "<table border=1><tr>";
	s += "<td>№</td>";
	s += "<td>Тип</td>";
	s += "<td>Статус</td>";
	s += "<td>Форма</td>";
	s += "</tr>";

	// **************************
	// * тестовое SMS-сообщение *
	// **************************
	s += "<tr>";

	// №
	s += "<td>0</td>";

	// тип
	s += "<td>";
	if (iTuningIconSize == 16)
		s += "<img src=\"/images/16x16/appointment-soon.png\" title=\"Тестовое SMS-сообщение\" border=0>";
	else
		s += "<img src=\"/images/24x24/appointment-soon.png\" title=\"Тестовое SMS-сообщение\" border=0>";
	s += "</td>";

	// статус
	s += "<td>";
	if (config.bSmsCreatePausedType0)
	{
		if (iTuningIconSize == 16)
			s += "<img src=\"/images/16x16/agt_resume.png\" title=\"Пауза при создании\" border=0>";
		else
			s += "<img src=\"/images/24x24/agt_resume.png\" title=\"Пауза при создании\" border=0>";
	}
	s += "</td>";

	// форма
	s += "<td><form action=\"/sms_test_accept\" method=post>";
	s += "Тестовое SMS-сообщение, номер: <input type=text name=sms_phone size=20 maxlength=20 value=\"\">";
	s += ", <nobr>текст: <input type=text name=sms_text size=40 maxlength=256 value=\"\">";
	// получаем номер транзакции и передаём его в скрытых параметрах
	iAction = actions->fnAdd();
	s += "<input type=hidden name=action value=\"";
	sprintf(szTmp,"%i",iAction);
	s += szTmp;
	s += "\">";
	s += "&nbsp;<input type=submit value=\"Отправить\"></nobr></form></td>";

	s += "</tr>";

	// **********************
	// * выдача результатов *
	// **********************
	s += "<tr>";

	// №
	s += "<td>1</td>";

	// тип
	s += "<td>";
	if (iTuningIconSize == 16)
		s += "<img src=\"/images/16x16/edit-paste.png\" title=\"Выдача результатов\" border=0>";
	else
		s += "<img src=\"/images/24x24/edit-paste.png\" title=\"Выдача результатов\" border=0>";
	s += "</td>";

	// статус
	s += "<td>";
	if (config.bSmsCreatePausedType1)
	{
		if (iTuningIconSize == 16)
			s += "<img src=\"/images/16x16/agt_resume.png\" title=\"Пауза при создании\" border=0>";
		else
			s += "<img src=\"/images/24x24/agt_resume.png\" title=\"Пауза при создании\" border=0>";
	}
	s += "</td>";

	// форма
	s += "<td>";
	s += "<div>Форма для создания SMS-сообщений о готовности результатов не существует. \
Такие сообщения создаются автоматически планировщиком выдачи результатов.</div>";

	s += "<div>Текст сообщения планировщика выдачи результатов (текущий):</div>";
	s += "<div><i>";
	s += config.szSmsGivingResults;
	s += "</i></div>";
	s += "</td>";

	s += "</tr>";

	// *****************************************
	// * рассылка поздравлений с днём рождения *
	// *****************************************
	s += "<tr>";

	// №
	s += "<td>2</td>";

	// тип
	s += "<td>";
	if (iTuningIconSize == 16)
		s += "<img src=\"/images/16x16/face-smile.png\" title=\"Поздравление с днём рождения\" border=0>";
	else
		s += "<img src=\"/images/24x24/face-smile.png\" title=\"Поздравление с днём рождения\" border=0>";
	s += "</td>";

	// статус
	s += "<td>";
	if (config.bSmsCreatePausedType2)
	{
		if (iTuningIconSize == 16)
			s += "<img src=\"/images/16x16/agt_resume.png\" title=\"Пауза при создании\" border=0>";
		else
			s += "<img src=\"/images/24x24/agt_resume.png\" title=\"Пауза при создании\" border=0>";
	}
	s += "</td>";

	// форма
	s += "<td>";
	s += "<div><form action=\"/sms_birthday_accept\" method=post>";
	s += "Поздравления с днём рождения:";
	query->dst->fnAddLine(s.c_str());
	fnSelectDate(query,0,szToday);
	// получаем номер транзакции и передаём его в скрытых параметрах
	iAction = actions->fnAdd();
	s += "<input type=hidden name=action value=\"";
	sprintf(szTmp,"%i",iAction);
	s += szTmp;
	s += "\">";
	s = "<input type=submit value=\"Разослать\"></form></div>";

	s += "<div>Текст поздравления с днём рождения (текущий):</div>";
	s += "<div><i>";
	s += config.szSmsBirthday;
	s += "</i></div>";
	s += "</td>";

	s += "</tr>";

	// ***********************
	// * информация клиентам *
	// ***********************
	s += "<tr>";

	// №
	s += "<td>3</td>";

	// тип
	s += "<td>";
	if (iTuningIconSize == 16)
		s += "<img src=\"/images/16x16/attach.png\" title=\"Информация клиентам\" border=0>";
	else
		s += "<img src=\"/images/24x24/attach.png\" title=\"Информация клиентам\" border=0>";
	s += "</td>";

	// статус
	s += "<td>";
	if (config.bSmsCreatePausedType3)
	{
		if (iTuningIconSize == 16)
			s += "<img src=\"/images/16x16/agt_resume.png\" title=\"Пауза при создании\" border=0>";
		else
			s += "<img src=\"/images/24x24/agt_resume.png\" title=\"Пауза при создании\" border=0>";
	}
	s += "</td>";

	// форма
	s += "<td><form action=\"/sms_info_accept\" method=post>";
	s += "Рассылка информации клиентам: период с";
	query->dst->fnAddLine(s.c_str());
	// дата 1
	fnSelectDate(query,"from_",szToday);
	query->dst->fnAddLine("по");
	// дата 2
	fnSelectDate(query,"to_",szToday);
	query->dst->fnAddLine(", <nobr>группа");
	fnServicesGroupsSelectFilter(query,-1);
	s = "</nobr>, <nobr>текст: <input type=text name=sms_text size=40 maxlength=256 value=\"\">";
	// получаем номер транзакции и передаём его в скрытых параметрах
	iAction = actions->fnAdd();
	s += "<input type=hidden name=action value=\"";
	sprintf(szTmp,"%i",iAction);
	s += szTmp;
	s += "\">";
	s += "&nbsp;<input type=submit value=\"Разослать\"></nobr></form></td>";

	s += "</tr>";

	s += "</table></p>";
	query->dst->fnAddLine(s.c_str());

	fnFormEnd(query);
}

void fnAppSmsActionAccept(QUERY *query)
{
#if defined(__WATCOMC__)
	std::auto_ptr<ARGS_CHECK> check (new ARGS_CHECK("fnAppSmsActionAccept"));
#else
	std::unique_ptr<ARGS_CHECK> check (new ARGS_CHECK("fnAppSmsActionAccept"));
#endif // defined
	ARG_OPTIONS s_arg_options;
	string s;
	RING *args = query->args;
	char sz [INI_MAX_LEXEM_LEN +1];
	int iAction = 0;
	int iSmsAction = 0;

	int iQty;
	int iCycle;
	SMS s_sms;
	bool bDelete;
	int iCounter = 0;
	char szTmp [20];

	// action
	check->fnSetArg(&s_arg_options,"action",true);
	check->fnSetValueLen(&s_arg_options,1,8);
	check->fnSetValueValidChars(&s_arg_options,ARG_DIGITS);
	check->fnAdd(&s_arg_options);
	// sms_action
	check->fnSetArg(&s_arg_options,"sms_action",true);
	check->fnSetValueLen(&s_arg_options,1,2);
	check->fnSetValueValidChars(&s_arg_options,ARG_DIGITS);
	check->fnAdd(&s_arg_options);

	switch(check->fnCheckDebug(s,args)) // контроль аргументов
	{
		case ARGS_CHECK_RESULT_ERROR_CRITICAL:
//		fnAppErrorArgument(query,s.c_str());
		return;

		case ARGS_CHECK_RESULT_ERROR:
		case ARGS_CHECK_RESULT_MESSAGE:
		break;
	}

	while (args->fnGetQty())
	{
		args->fnPop(sz);
		if (! strcmp(sz,"action"))
		{
			args->fnPop(sz);
			iAction = atoi(sz);
			continue;
		}
		if (! strcmp(sz,"sms_action"))
		{
			args->fnPop(sz);
			iSmsAction = atoi(sz);
			continue;
		}
	}

	// проверяем, выполнена ли транзакция
	if (actions->fnCheck(iAction))
		return;

	switch (iSmsAction)
	{
		// *****************************************
		// * раздел "Групповые операции обработки" *
		// *****************************************
		case 1: // снять паузу с SMS-сообщений типа 0 (тестовые сообщения)
		iQty = sms->fnGetQty();
		for (iCycle = 0; iCycle < iQty; iCycle++)
		{
			sms->fnPop((char *) &s_sms,iCycle); // неразрушающее чтение

			if (s_sms.iType != 0)
				continue;
			if (s_sms.bPaused == false)
				continue;

			s_sms.bPaused = false;
			sms->fnReplace((char *) &s_sms,iCycle,sizeof (SMS)); // заменяем запись в кольце
			iCounter++;
		}
		s = "Снятие паузы с SMS-сообщений типа 0 (тестовые сообщения). Снято с паузы";
		sprintf(szTmp,": %i.",iCounter);
		s += szTmp;
		fnCreateErrorMessage(query,2,3,s.c_str());
		break;

		case 2: // снять паузу с SMS-сообщений типа 1 (сообщения планировщика выдачи результатов)
		iQty = sms->fnGetQty();
		for (iCycle = 0; iCycle < iQty; iCycle++)
		{
			sms->fnPop((char *) &s_sms,iCycle); // неразрушающее чтение

			if (s_sms.iType != 1)
				continue;
			if (s_sms.bPaused == false)
				continue;

			s_sms.bPaused = false;
			sms->fnReplace((char *) &s_sms,iCycle,sizeof (SMS)); // заменяем запись в кольце
			iCounter++;
		}
		s = "Снятие паузы с SMS-сообщений типа 1 (сообщения планировщка выдачи результатов). Снято с паузы";
		sprintf(szTmp,": %i.",iCounter);
		s += szTmp;
		fnCreateErrorMessage(query,2,3,s.c_str());
		break;

		case 3: // снять паузу с SMS-сообщений типа 2 (поздравления с днём рождения)
		iQty = sms->fnGetQty();
		for (iCycle = 0; iCycle < iQty; iCycle++)
		{
			sms->fnPop((char *) &s_sms,iCycle); // неразрушающее чтение

			if (s_sms.iType != 2)
				continue;
			if (s_sms.bPaused == false)
				continue;

			s_sms.bPaused = false;
			sms->fnReplace((char *) &s_sms,iCycle,sizeof (SMS)); // заменяем запись в кольце
			iCounter++;
		}
		s = "Снятие паузы с SMS-сообщений типа 2 (поздравления с днём рождения). Снято с паузы";
		sprintf(szTmp,": %i.",iCounter);
		s += szTmp;
		fnCreateErrorMessage(query,2,3,s.c_str());
		break;

		case 4: // снять паузу с SMS-сообщений типа 3 (информационные сообщения)
		iQty = sms->fnGetQty();
		for (iCycle = 0; iCycle < iQty; iCycle++)
		{
			sms->fnPop((char *) &s_sms,iCycle); // неразрушающее чтение

			if (s_sms.iType != 3)
				continue;
			if (s_sms.bPaused == false)
				continue;

			s_sms.bPaused = false;
			sms->fnReplace((char *) &s_sms,iCycle,sizeof (SMS)); // заменяем запись в кольце
			iCounter++;
		}
		s = "Снятие паузы с SMS-сообщений типа 3 (информационные сообщения). Снято с паузы";
		sprintf(szTmp,": %i.",iCounter);
		s += szTmp;
		fnCreateErrorMessage(query,2,3,s.c_str());
		break;

		case 9: // поставить на паузу SMS-сообщения с шагом доставки 0
		iQty = sms->fnGetQty();
		for (iCycle = 0; iCycle < iQty; iCycle++)
		{
			sms->fnPop((char *) &s_sms,iCycle); // неразрушающее чтение

			if (s_sms.iStep != 0)
				continue;
			if (s_sms.bPaused == true)
				continue;

			s_sms.bPaused = true;
			sms->fnReplace((char *) &s_sms,iCycle,sizeof (SMS)); // заменяем запись в кольце
			iCounter++;
		}
		s = "Постановка на паузу SMS-сообщений с шагом доставки 0. Поставлено на паузу";
		sprintf(szTmp,": %i.",iCounter);
		s += szTmp;
		fnCreateErrorMessage(query,2,3,s.c_str());
		break;

		// ****************************************
		// * раздел "Групповые операции удаления" *
		// ****************************************
		case 5: // удалить отправленные SMS-сообщения
		iQty = sms->fnGetQty();
		for (iCycle = 0; iCycle < iQty; iCycle++)
		{
			sms->fnPop((char *) &s_sms); // разрушающее чтение

			if (s_sms.bSuccess == false)
				sms->fnPush((char *) &s_sms,sizeof (SMS)); // возвращаем запись в кольцо
			else
				iCounter++;
		}
		s = "Удаление отправленных SMS-сообщений. Удалено";
		sprintf(szTmp,": %i.",iCounter);
		s += szTmp;
		fnCreateErrorMessage(query,2,3,s.c_str());
		break;

		case 6: // удалить неотправленные SMS-сообщения
		iQty = sms->fnGetQty();
		for (iCycle = 0; iCycle < iQty; iCycle++)
		{
			sms->fnPop((char *) &s_sms); // разрушающее чтение

			if (s_sms.bSuccess == true)
				sms->fnPush((char *) &s_sms,sizeof (SMS)); // возвращаем запись в кольцо
			else
				iCounter++;
		}
		s = "Удаление неотправленных SMS-сообщений. Удалено";
		sprintf(szTmp,": %i.",iCounter);
		s += szTmp;
		fnCreateErrorMessage(query,2,3,s.c_str());
		break;

		case 7: // удалить SMS-сообщения, обработка которых завершена
		iQty = sms->fnGetQty();
		for (iCycle = 0; iCycle < iQty; iCycle++)
		{
			sms->fnPop((char *) &s_sms); // разрушающее чтение

			if (s_sms.bCompleted == false)
				sms->fnPush((char *) &s_sms,sizeof (SMS)); // возвращаем запись в кольцо
			else
				iCounter++;
		}
		s = "Удаление SMS-сообщений, обработка которых завершена. Удалено";
		sprintf(szTmp,": %i.",iCounter);
		s += szTmp;
		fnCreateErrorMessage(query,2,3,s.c_str());
		break;

		case 8: // удалить все SMS-сообщения
		iCounter = sms->fnGetQty();
		sms->fnClear();
		s = "Удаление всех SMS-сообщений. Удалено";
		sprintf(szTmp,": %i.",iCounter);
		s += szTmp;
		fnCreateErrorMessage(query,2,3,s.c_str());
		break;

		case 10: // удалить SMS-сообщения типа 0 (тестовые сообщения), с шагом доставки 0
		iQty = sms->fnGetQty();
		for (iCycle = 0; iCycle < iQty; iCycle++)
		{
			sms->fnPop((char *) &s_sms); // разрушающее чтение

			bDelete = false;
			do
			{
				if (s_sms.iType != 0)
					continue;
				if (s_sms.iStep != 0)
					continue;

				bDelete = true;
			} while (false);

			if (! bDelete)
				sms->fnPush((char *) &s_sms,sizeof (SMS)); // возвращаем запись в кольцо
			else
				iCounter++;
		}
		s = "Удаление SMS-сообщений типа 0 (тестовые сообщения), с шагом доставки 0. Удалено";
		sprintf(szTmp,": %i.",iCounter);
		s += szTmp;
		fnCreateErrorMessage(query,2,3,s.c_str());
		break;

		case 11: // удалить SMS-сообщения типа 1 (сообщения планировщика выдачи результатов), с шагом доставки 0
		iQty = sms->fnGetQty();
		for (iCycle = 0; iCycle < iQty; iCycle++)
		{
			sms->fnPop((char *) &s_sms); // разрушающее чтение

			bDelete = false;
			do
			{
				if (s_sms.iType != 1)
					continue;
				if (s_sms.iStep != 0)
					continue;

				bDelete = true;
			} while (false);

			if (! bDelete)
				sms->fnPush((char *) &s_sms,sizeof (SMS)); // возвращаем запись в кольцо
			else
				iCounter++;
		}
		s = "Удаление SMS-сообщений типа 1 (сообщения планировщика выдачи результатов), с шагом доставки 0. Удалено";
		sprintf(szTmp,": %i.",iCounter);
		s += szTmp;
		fnCreateErrorMessage(query,2,3,s.c_str());
		break;

		case 12: // удалить SMS-сообщения типа 2 (поздравления с днём рождения), с шагом доставки 0
		iQty = sms->fnGetQty();
		for (iCycle = 0; iCycle < iQty; iCycle++)
		{
			sms->fnPop((char *) &s_sms); // разрушающее чтение

			bDelete = false;
			do
			{
				if (s_sms.iType != 2)
					continue;
				if (s_sms.iStep != 0)
					continue;

				bDelete = true;
			} while (false);

			if (! bDelete)
				sms->fnPush((char *) &s_sms,sizeof (SMS)); // возвращаем запись в кольцо
			else
				iCounter++;
		}
		s = "Удаление SMS-сообщений типа 2 (поздравления с днём рождения), с шагом доставки 0. Удалено";
		sprintf(szTmp,": %i.",iCounter);
		s += szTmp;
		fnCreateErrorMessage(query,2,3,s.c_str());
		break;

		case 13: // удалить SMS-сообщения типа 3 (информационные сообщения), с шагом доставки 0
		iQty = sms->fnGetQty();
		for (iCycle = 0; iCycle < iQty; iCycle++)
		{
			sms->fnPop((char *) &s_sms); // разрушающее чтение

			bDelete = false;
			do
			{
				if (s_sms.iType != 3)
					continue;
				if (s_sms.iStep != 0)
					continue;

				bDelete = true;
			} while (false);

			if (! bDelete)
				sms->fnPush((char *) &s_sms,sizeof (SMS)); // возвращаем запись в кольцо
			else
				iCounter++;
		}
		s = "Удаление SMS-сообщений типа 3 (информационные сообщения), с шагом доставки 0. Удалено";
		sprintf(szTmp,": %i.",iCounter);
		s += szTmp;
		fnCreateErrorMessage(query,2,3,s.c_str());
		break;
	}
}

/*
const int SMS_VERSION = 4;
struct SMS
{
	int iVersion;
	int iId; // идентификатор
	// тип:
	// 0 - тест
	// 1 - выдача результатов
	// 2 - поздравление с днём рождения
	// 3 - рассылка информации клиентам
	int iType;
	int iStep; // шаг обработки
	// время создания записи
	union
	{
		FILETIME ft_create;
		__int64 time64_create;
	};
	char szDate [11]; // дата создания
	char szTime [5 +1]; // время создания
	char szPrefix [WS_MAX_NAME_LEN +1]; // префикс отправителя
	char szPhone [11 +1]; // номер телефона получателя в формате 71234567890
	char szText [256 +1]; // текст сообщения в кодировке 1251
	int iClientNum; // тип 1,2,3; уникальный номер клиента
	int iContractNum; // тип 1: номер документа
	char szContractDate [11]; // тип 1: дата оформления документа
	bool bAction; // обработка начата
	// время последней обработки
	union
	{
		FILETIME ft_action;
		__int64 time64_action;
	};
	char szStepTime [5 +1];
	// ответ сервиса
	char szServiceAnswer [64 +1];
	int iSmsNum; // идентификатор сообщения
	bool bError; // ошибка, ожидание
	bool bCompleted; // операции завершены
	bool bSuccess; // успешно
	int iGate; // используемый шлюз (0 - не выбран)
	int iErrorCode; // 1000sms.ru
	bool bPaused; // пауза в обработке сообщения
};
*/

static bool fnSearchBirthdaySms(int iClientNum)
{
	int iQty;
	int iCycle;
	SMS s_sms;

	iQty = sms->fnGetQty();
	for (iCycle = 0; iCycle < iQty; iCycle++)
	{
		sms->fnPop((char *) &s_sms,iCycle);

		if ((s_sms.iType == 2) // поздравление с днём рождения
			&& (s_sms.iClientNum == iClientNum))
			return true;
	}
	return false;
}

void fnAppSmsBirthdayAccept(QUERY *query)
{
#if defined(__WATCOMC__)
	std::auto_ptr<ARGS_CHECK> check (new ARGS_CHECK("fnAppSmsBirthdayAccept"));
#else
	std::unique_ptr<ARGS_CHECK> check (new ARGS_CHECK("fnAppSmsBirthdayAccept"));
#endif // defined
	ARG_OPTIONS s_arg_options;
	string s;
	RING *args = query->args;
	char sz [INI_MAX_LEXEM_LEN +1];
	int iAction = 0;
	int iDay = 0;
	int iMonth = 0;

	int iCycle;
	int iClientNum;
	int iCounter;
	char szTmp [20];

	// action
	check->fnSetArg(&s_arg_options,"action",true);
	check->fnSetValueLen(&s_arg_options,1,8);
	check->fnSetValueValidChars(&s_arg_options,ARG_DIGITS);
	check->fnAdd(&s_arg_options);
	// day
	check->fnSetArg(&s_arg_options,"day",true);
	check->fnSetValueLen(&s_arg_options,1,2);
	check->fnSetValueValidChars(&s_arg_options,ARG_DIGITS);
	check->fnAdd(&s_arg_options);
	// month
	check->fnSetArg(&s_arg_options,"month",true);
	check->fnSetValueLen(&s_arg_options,1,2);
	check->fnSetValueValidChars(&s_arg_options,ARG_DIGITS);
	check->fnAdd(&s_arg_options);
	// year
	check->fnSetArg(&s_arg_options,"year",true);
	check->fnSetValueLen(&s_arg_options,4,4);
	check->fnSetValueValidChars(&s_arg_options,ARG_DIGITS);
	check->fnAdd(&s_arg_options);

	switch(check->fnCheckDebug(s,args)) // контроль аргументов
	{
		case ARGS_CHECK_RESULT_ERROR_CRITICAL:
//		fnAppErrorArgument(query,s.c_str());
		return;

		case ARGS_CHECK_RESULT_ERROR:
		case ARGS_CHECK_RESULT_MESSAGE:
		break;
	}

	while (args->fnGetQty())
	{
		args->fnPop(sz);
		if (! strcmp(sz,"action"))
		{
			args->fnPop(sz);
			iAction = atoi(sz);
			continue;
		}
		if (! strcmp(sz,"day"))
		{
			args->fnPop(sz);
			iDay = atoi(sz);
			continue;
		}
		if (! strcmp(sz,"month"))
		{
			args->fnPop(sz);
			iMonth = atoi(sz);
			continue;
		}
		if (! strcmp(sz,"year"))
		{
			args->fnPop(sz);
			// не используется, поэтому не сохраняем
			continue;
		}
	}

	// проверяем, выполнена ли транзакция
	if (actions->fnCheck(iAction))
		return;

	// цикл с единицы, т.к. нулевой клиент - Анонимный
	for (iCycle = 1; iCycle < iClientsQty; iCycle++)
	{
		// пропускаем удалённые записи
		if (client [iCycle]->fnGetDelete())
			continue;
		if (client [iCycle]->fnGetBanOnSMS())
			continue;
//		if (! client [iCycle]->fnGetConsentToReceiveSMS())
//			continue;
		// пропускаем клиентов с некорректной датой рождения
		if (! fnCheckDate(client [iCycle]->fnGetBirthday()))
			continue;
		// пропускаем клиентов, которые родились в другой день
		if ((fnGetDay(client [iCycle]->fnGetBirthday()) != iDay)
			|| (fnGetMonth(client [iCycle]->fnGetBirthday()) != iMonth))
			continue;
		iClientNum = client [iCycle]->fnGetNum();
		// пропускаем клиентов, для которых есть поздравления в базе sms-сообщений
		if (fnSearchBirthdaySms(iClientNum))
			continue;

		// пытаемся отправить sms
		if (fnSendSmsBirthday(query,iClientNum))
			iCounter++;
	}

	s = "Рассылка поздравлений с днём рождения. Сообщений";
	sprintf(szTmp,": %i.",iCounter);
	s += szTmp;
	fnCreateErrorMessage(query,2,4,s.c_str());
}

void fnAppSmsEdit(QUERY *query)
{
#if defined(__WATCOMC__)
	std::auto_ptr<ARGS_CHECK> check (new ARGS_CHECK("fnAppSmsEdit"));
#else
	std::unique_ptr<ARGS_CHECK> check (new ARGS_CHECK("fnAppSmsEdit"));
#endif // defined
	ARG_OPTIONS s_arg_options;
	string s;
	RING *args = query->args;
	char sz [INI_MAX_LEXEM_LEN +1];
	int iSmsId = 0;
	int iQty;
	int iCycle;
	SMS s_sms;
	bool bFound = false;
	char szTmp [20];
	int iClient;
	char szClientFIO [100]; // для вывода FIO

	// sms_id
	check->fnSetArg(&s_arg_options,"sms_id",true);
	check->fnSetValueLen(&s_arg_options,1,6);
	check->fnSetValueValidChars(&s_arg_options,ARG_DIGITS);
	check->fnAdd(&s_arg_options);

	switch(check->fnCheckDebug(s,args)) // контроль аргументов
	{
		case ARGS_CHECK_RESULT_ERROR_CRITICAL:
		fnAppErrorArgument(query,s.c_str());
		return;

		case ARGS_CHECK_RESULT_ERROR:
		case ARGS_CHECK_RESULT_MESSAGE:
		break;
	}

	while (args->fnGetQty())
	{
		args->fnPop(sz);
		if (! strcmp(sz,"sms_id"))
		{
			args->fnPop(sz);
			iSmsId = atoi(sz);
			continue;
		}
	}

	// поиск SMS-сообщения
	iQty = sms->fnGetQty();
	for (iCycle = 0; iCycle < iQty; iCycle++)
	{
		sms->fnPop((char *) &s_sms,iCycle);

		if (s_sms.iId == iSmsId)
		{
			bFound = true;
			break;
		}
	}
	if (! bFound)
		return;

	fnFormContent(query);
	query->dst->fnAddLine("<p>Редактирование SMS-сообщения</p>");

	s = "<form action=\"/sms_edit_accept\" method=post><table border=0>";

	// id
	s += "<tr><td>id:</td><td>";
	sprintf(szTmp,"%i",s_sms.iId);
	s += szTmp;
	s += "</td></tr>";

	// тип
	s += "<tr><td>Тип:</td><td>";
	switch (s_sms.iType)
	{
		case 0: s += "Тестовое SMS-сообщение"; break;
		case 1: s += "Выдача результатов"; break;
		case 2: s += "Поздравление с днём рождения"; break;
	}
	s += "</td></tr>";

	// пауза
	s += "<tr><td>Пауза:</td><td><input type=checkbox name=paused value=\"1\"";
	if (s_sms.bPaused)
		s += " checked";
	s += "></td></tr>";

	// используемый шлюз
	s += "<tr><td>Используемый шлюз:</td><td>";
	switch (s_sms.iGate)
	{
		case SMS_GATE_1000: s += "1000sms.ru"; break;
		case SMS_GATE_AERO: s += "smsaero.ru"; break;
		default:
		sprintf(szTmp,"%i",s_sms.iGate);
		s += szTmp;
	}
	s += "</td></tr>";

	// шаг обработки
	s += "<tr><td>Шаг обработки:</td><td>";
	sprintf(szTmp,"%i",s_sms.iStep);
	s += szTmp;
	s += "</td></tr>";

	// повторить обработку
	s += "<tr><td>Повторить обработку:</td><td><input type=checkbox name=restart value=\"1\"></td></tr>";

	// дата
	s += "<tr><td>Дата:</td><td>";
	s += s_sms.szDate;
	s += "</td></tr>";

	// время
	s += "<tr><td>Время:</td><td>";
	s += s_sms.szTime;
	s += "</td></tr>";

	// префикс отправителя
	s += "<tr><td>Префикс отправителя:</td><td>";
	s += s_sms.szPrefix;
	s += "</td></tr>";

	// клиент
	switch (s_sms.iType)
	{
		case 1:
		case 2:
		case 3:
		s += "<tr><td>Клиент:</td><td>";
		iClient = fnClientSearchByNum(s_sms.iClientNum);
		if (iClient == -1)
		{
			fnAppErrorArgument(query);
			return;
		}
		sprintf(szClientFIO,"%s %s %s",
			client [iClient]->fnGetFamily(),
			client [iClient]->fnGetName(),
			client [iClient]->fnGetName2());
		s += szClientFIO;
		s += "</td></tr>";
	}

	// номер телефона получателя
	s += "<tr><td>Номер телефона:</td><td>";
	s += s_sms.szPhone;
	s += "</td></tr>";

	// текст сообщения
	s += "<tr><td>Текст сообщения:</td><td>";
	s += s_sms.szText;
	s += "</td></tr>";

	// отменить обработку
	s += "<tr><td>Отменить обработку:</td><td><input type=checkbox name=stop value=\"1\"></td></tr>";

	// операции завершены
	s += "<tr><td>Операции завершены:</td><td>";
	if (s_sms.bCompleted)
		s += "Да";
	s += "</td></tr>";

	// результат
	s += "<tr><td>Успешно:</td><td>";
	if (s_sms.bSuccess)
		s += "Да";
	else
		s += "Нет";
	s += "</td></tr>";

	// код ошибки iErrorCode
	do
	{
		if (s_sms.iGate == SMS_GATE_AERO)
			continue;

		s += "<tr><td>Код ошибки:</td><td>";
		sprintf(szTmp,"%i",s_sms.iErrorCode);
		s += szTmp;
		s += "</td></tr>";
	} while (false);

	// в скрытых параметрах id
	s += "<tr><td><input type=hidden name=sms_id value=\"";
	sprintf(szTmp,"%i",s_sms.iId);
	s += szTmp;
	s += "\"></td><td><input type=submit value=\"Применить\"></td></tr>";
	s += "</table></form>";
	query->dst->fnAddLine(s.c_str ());

	fnFormEnd(query);
}

void fnAppSmsEditAccept(QUERY *query)
{
#if defined(__WATCOMC__)
	std::auto_ptr<ARGS_CHECK> check (new ARGS_CHECK("fnAppSmsEditAccept"));
#else
	std::unique_ptr<ARGS_CHECK> check (new ARGS_CHECK("fnAppSmsEditAccept"));
#endif // defined
	ARG_OPTIONS s_arg_options;
	string s;
	RING *args = query->args;
	char sz [INI_MAX_LEXEM_LEN +1];
	bool bPaused = false;
	bool bRestart = false;
	int iSmsId = 0;
	bool bStop = false;

	int iQty;
	int iCycle;
	SMS s_sms;
	bool bReplace = false;

	// paused
	check->fnSetArg(&s_arg_options,"paused",false);
	check->fnSetValueLen(&s_arg_options,1,1);
	check->fnSetValueValidChars(&s_arg_options,0,"1");
	check->fnAdd(&s_arg_options);
	// restart
	check->fnSetArg(&s_arg_options,"restart",false);
	check->fnSetValueLen(&s_arg_options,1,1);
	check->fnSetValueValidChars(&s_arg_options,0,"1");
	check->fnAdd(&s_arg_options);
	// sms_id
	check->fnSetArg(&s_arg_options,"sms_id",true);
	check->fnSetValueLen(&s_arg_options,1,6);
	check->fnSetValueValidChars(&s_arg_options,ARG_DIGITS);
	check->fnAdd(&s_arg_options);
	// stop
	check->fnSetArg(&s_arg_options,"stop",false);
	check->fnSetValueLen(&s_arg_options,1,1);
	check->fnSetValueValidChars(&s_arg_options,0,"1");
	check->fnAdd(&s_arg_options);

	switch(check->fnCheckDebug(s,args)) // контроль аргументов
	{
		case ARGS_CHECK_RESULT_ERROR_CRITICAL:
//		fnAppErrorArgument(query,s.c_str());
		return;

		case ARGS_CHECK_RESULT_ERROR:
		case ARGS_CHECK_RESULT_MESSAGE:
		break;
	}

	while (args->fnGetQty())
	{
		args->fnPop(sz);
		if (! strcmp(sz,"paused"))
		{
			args->fnPop(sz);
			bPaused = true;
			continue;
		}
		if (! strcmp(sz,"restart"))
		{
			args->fnPop(sz);
			bRestart = true;
			continue;
		}
		if (! strcmp(sz,"sms_id"))
		{
			args->fnPop(sz);
			iSmsId = atoi(sz);
			continue;
		}
		if (! strcmp(sz,"stop"))
		{
			args->fnPop(sz);
			bStop = true;
			continue;
		}
	}

	iQty = sms->fnGetQty();
	for (iCycle = 0; iCycle < iQty; iCycle++)
	{
		sms->fnPop((char *) &s_sms,iCycle);

		if (s_sms.iId == iSmsId)
		{
			if (s_sms.bPaused != bPaused)
			{
				bReplace = true;

				s_sms.bPaused = bPaused;
			}
			if (bRestart)
			{
				bReplace = true;

				s_sms.iStep = 0;
				s_sms.bAction = false;
				*s_sms.szServiceAnswer = 0;
				s_sms.iSmsNum = 0;
				s_sms.bError = false;
				s_sms.bCompleted = false;
				s_sms.bSuccess = false;
				s_sms.iGate = 0;
			}
			if (bStop)
			{
				bReplace = true;

				s_sms.bCompleted = true;
			}

			if (bReplace)
				sms->fnReplace((char *) &s_sms,iCycle,sizeof (SMS));
			break;
		}
	}
}

void fnAppSmsFilterAccept(QUERY *query)
{
#if defined(__WATCOMC__)
	std::auto_ptr<ARGS_CHECK> check (new ARGS_CHECK("fnAppSmsFilterAccept"));
#else
	std::unique_ptr<ARGS_CHECK> check (new ARGS_CHECK("fnAppSmsFilterAccept"));
#endif // defined
	ARG_OPTIONS s_arg_options;
	string s;
	RING *args = query->args;
	char sz [INI_MAX_LEXEM_LEN +1];
	int iDays = 7;

	char szTmp [20];

	// days
	check->fnSetArg(&s_arg_options,"days",true);
	check->fnSetValueLen(&s_arg_options,1,2);
	check->fnSetValueValidChars(&s_arg_options,ARG_DIGITS);
	check->fnAdd(&s_arg_options);

	switch(check->fnCheckDebug(s,args)) // контроль аргументов
	{
		case ARGS_CHECK_RESULT_ERROR_CRITICAL:
//		fnAppErrorArgument(query,s.c_str());
		return;

		case ARGS_CHECK_RESULT_ERROR:
		case ARGS_CHECK_RESULT_MESSAGE:
		break;
	}

	while (args->fnGetQty())
	{
		args->fnPop(sz);
		if (! strcmp(sz,"days"))
		{
			args->fnPop(sz);
			iDays = atoi(sz);
			continue;
		}
	}
	// загружаем параметры фильтра в ассоциативный массив
	sprintf(szTmp,"%i",iDays);
	login_days->fnAdd(query->szLogin,szTmp);
}

void fnAppSmsInfoAccept(QUERY *query)
{
#if defined(__WATCOMC__)
	std::auto_ptr<ARGS_CHECK> check (new ARGS_CHECK("fnAppSmsInfoAccept"));
#else
	std::unique_ptr<ARGS_CHECK> check (new ARGS_CHECK("fnAppSmsInfoAccept"));
#endif // defined
	ARG_OPTIONS s_arg_options;
	string s;
	RING *args = query->args;
	char sz [INI_MAX_LEXEM_LEN +1];
	int iAction = 0;
	int iDay1 = 0;
	int iMonth1 = 0;
	int iYear1 = 0;
	char szGroupId [2 +1];
	char szText [256 +1] = "";
	int iDay2 = 0;
	int iMonth2 = 0;
	int iYear2 = 0;

	int iGroupId = 0;
	char szDate1 [11];
	char szDate2 [11];
	int iCycle;
	int iClientNum;
#if defined(__WATCOMC__)
	std::auto_ptr<A_KEY_INT> clients_nums (new A_KEY_INT("fnAppSmsInfoAccept(QUERY *): clients_nums",16*1024));
#else
	std::unique_ptr<A_KEY_INT> clients_nums (new A_KEY_INT("fnAppSmsInfoAccept(QUERY *): clients_nums",16*1024));
#endif // defined
	int iQty;
	int iCounter = 0;
	char szTmp [20];

	// action
	check->fnSetArg(&s_arg_options,"action",true);
	check->fnSetValueLen(&s_arg_options,1,8);
	check->fnSetValueValidChars(&s_arg_options,ARG_DIGITS);
	check->fnAdd(&s_arg_options);
	// from_day
	check->fnSetArg(&s_arg_options,"from_day",true);
	check->fnSetValueLen(&s_arg_options,1,2);
	check->fnSetValueValidChars(&s_arg_options,ARG_DIGITS);
	check->fnAdd(&s_arg_options);
	// from_month
	check->fnSetArg(&s_arg_options,"from_month",true);
	check->fnSetValueLen(&s_arg_options,1,2);
	check->fnSetValueValidChars(&s_arg_options,ARG_DIGITS);
	check->fnAdd(&s_arg_options);
	// from_year
	check->fnSetArg(&s_arg_options,"from_year",true);
	check->fnSetValueLen(&s_arg_options,4,4);
	check->fnSetValueValidChars(&s_arg_options,ARG_DIGITS);
	check->fnAdd(&s_arg_options);
	// group
	check->fnSetArg(&s_arg_options,"group",true);
	check->fnSetValueLen(&s_arg_options,1,2);
	check->fnSetValueValidChars(&s_arg_options,ARG_DIGITS);
	check->fnAdd(&s_arg_options);
	// sms_text
	check->fnSetArg(&s_arg_options,"sms_text",true);
	check->fnSetValueLen(&s_arg_options,0,256);
	check->fnAdd(&s_arg_options);
	// to_day
	check->fnSetArg(&s_arg_options,"to_day",true);
	check->fnSetValueLen(&s_arg_options,1,2);
	check->fnSetValueValidChars(&s_arg_options,ARG_DIGITS);
	check->fnAdd(&s_arg_options);
	// to_month
	check->fnSetArg(&s_arg_options,"to_month",true);
	check->fnSetValueLen(&s_arg_options,1,2);
	check->fnSetValueValidChars(&s_arg_options,ARG_DIGITS);
	check->fnAdd(&s_arg_options);
	// to_year
	check->fnSetArg(&s_arg_options,"to_year",true);
	check->fnSetValueLen(&s_arg_options,4,4);
	check->fnSetValueValidChars(&s_arg_options,ARG_DIGITS);
	check->fnAdd(&s_arg_options);

	switch(check->fnCheckDebug(s,args)) // контроль аргументов
	{
		case ARGS_CHECK_RESULT_ERROR_CRITICAL:
//		fnAppErrorArgument(query,s.c_str());
		return;

		case ARGS_CHECK_RESULT_ERROR:
		case ARGS_CHECK_RESULT_MESSAGE:
		break;
	}

	while (args->fnGetQty())
	{
		args->fnPop(sz);
		if (! strcmp(sz,"action"))
		{
			args->fnPop(sz);
			iAction = atoi(sz);
			continue;
		}
		if (! strcmp(sz,"from_day"))
		{
			args->fnPop(sz);
			iDay1 = atoi(sz);
			continue;
		}
		if (! strcmp(sz,"from_month"))
		{
			args->fnPop(sz);
			iMonth1 = atoi(sz);
			continue;
		}
		if (! strcmp(sz,"from_year"))
		{
			args->fnPop(sz);
			iYear1 = atoi(sz);
			continue;
		}
		if (! strcmp(sz,"group"))
		{
			args->fnPop(szGroupId);
			iGroupId = atoi(szGroupId);
			continue;
		}
		if (! strcmp(sz,"sms_text"))
		{
			args->fnPop(szText);
			continue;
		}
		if (! strcmp(sz,"to_day"))
		{
			args->fnPop(sz);
			iDay2 = atoi(sz);
			continue;
		}
		if (! strcmp(sz,"to_month"))
		{
			args->fnPop(sz);
			iMonth2 = atoi(sz);
			continue;
		}
		if (! strcmp(sz,"to_year"))
		{
			args->fnPop(sz);
			iYear2 = atoi(sz);
			continue;
		}
	}
	sprintf(szDate1,"%02u/%02u/%04u",iDay1,iMonth1,iYear1);
	sprintf(szDate2,"%02u/%02u/%04u",iDay2,iMonth2,iYear2);
	fnNormalizeDate(szDate1);
	fnNormalizeDate(szDate2);

	// проверяем, выполнена ли транзакция
	if (actions->fnCheck(iAction))
		return;
	if (fnCompareDate(szDate1,szDate2) > 0)
		return;
	if (! strlen(szText))
		return;

	// выбираем актуальные посещения
	for (iCycle = 0; iCycle < iVisitsQty; iCycle++)
	{
		if (visit [iCycle]->fnGetDelete())
			continue;
		if (visit [iCycle]->fnGetPlannedTreatmentOnly())
			continue;
		if (! visit [iCycle]->fnGetClientNum()) // анонимный клиент
			continue;
		if (! fnCheckDateRange(szDate1,visit [iCycle]->fnGetDate (),szDate2))
			continue;
		if (visit [iCycle]->fnGetPreEntry())
			continue;
		// группа
		if (iGroupId >= 0)
			if (! fnServicesGroupsGetMember(visit [iCycle]->fnGetServiceType(),iGroupId))
				continue;

		// добавляем уникальный номер клиента в массив clients_found
		iClientNum = visit [iCycle]->fnGetClientNum();
		if (! clients_nums->fnCheck(iClientNum))
			clients_nums->fnAdd(iClientNum);
	}
	iQty = clients_nums->fnGetQty();

	if (iQty)
	{
		RING *buf;
		int iClientNum;
		int iClient;

		buf = new RING("fnAppSmsInfoAccept(QUERY *): buf",16*1024);
		clients_nums->fnCopyBuf(buf);
		for (iCycle = 0; iCycle < iQty; iCycle++)
		{
			buf->fnPop((char *) &iClientNum,iCycle);

			iClient = fnClientSearchByNum(iClientNum);
			if (iClient == -1)
				continue;
			// пропускаем удалённые записи
			if (client [iClient]->fnGetDelete())
				continue;
			if (client [iClient]->fnGetBanOnSMS())
				continue;
			if (! client [iClient]->fnGetConsentToReceiveSMS())
				continue;

			// помещаем SMS-сообщение в очередь для отправки
			if (fnSendSmsInfo(query,iClientNum,szText))
				iCounter++;
		}
		delete buf;
	}

	s = "Рассылка информации клиентам. Сообщений";
	sprintf(szTmp,": %i.",iCounter);
	s += szTmp;
	fnCreateErrorMessage(query,2,5,s.c_str());
}

void fnAppSmsTestAccept(QUERY *query)
{
#if defined(__WATCOMC__)
	std::auto_ptr<ARGS_CHECK> check (new ARGS_CHECK("fnAppSmsTestAccept"));
#else
	std::unique_ptr<ARGS_CHECK> check (new ARGS_CHECK("fnAppSmsTestAccept"));
#endif // defined
	ARG_OPTIONS s_arg_options;
	string s;
	RING *args = query->args;
	char sz [INI_MAX_LEXEM_LEN +1];
	int iAction = 0;
	char szFederalPhone [11 +1];
	bool bPhoneChecked = false;
	char szText [256 +1] = "";

	// action
	check->fnSetArg(&s_arg_options,"action",true);
	check->fnSetValueLen(&s_arg_options,1,8);
	check->fnSetValueValidChars(&s_arg_options,ARG_DIGITS);
	check->fnAdd(&s_arg_options);
	// sms_phone
	check->fnSetArg(&s_arg_options,"sms_phone",true);
	check->fnSetValueLen(&s_arg_options,0,20);
	check->fnSetValueValidChars(&s_arg_options,ARG_DIGITS);
	check->fnAdd(&s_arg_options);
	// sms_text
	check->fnSetArg(&s_arg_options,"sms_text",true);
	check->fnSetValueLen(&s_arg_options,0,256);
	check->fnAdd(&s_arg_options);

	switch(check->fnCheckDebug(s,args)) // контроль аргументов
	{
		case ARGS_CHECK_RESULT_ERROR_CRITICAL:
//		fnAppErrorArgument(query,s.c_str());
		return;

		case ARGS_CHECK_RESULT_ERROR:
		case ARGS_CHECK_RESULT_MESSAGE:
		break;
	}

	while (args->fnGetQty())
	{
		args->fnPop(sz);
		if (! strcmp(sz,"action"))
		{
			args->fnPop(sz);
			iAction = atoi(sz);
			continue;
		}
		if (! strcmp(sz,"sms_phone"))
		{
			args->fnPop(sz);
			bPhoneChecked = fnGetPhone(szFederalPhone,sz);
			continue;
		}
		if (! strcmp(sz,"sms_text"))
		{
			args->fnPop(szText);
			continue;
		}
	}

	// проверяем, выполнена ли транзакция
	if (actions->fnCheck(iAction))
		return;
	if (! bPhoneChecked)
		return;
	if (! strlen(szText))
		return;

	// помещаем SMS-сообщение в очередь для отправки
	fnSendSmsTest(query,szFederalPhone,szText);
}

На главную © Д.С. Кузьмин