Модуль рассылки SMS-сообщений, исходный текст (page_sms.cpp, модифицирован 03/02/2016)


#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" // fnGetToday ()
#include "other\md5.h"
#include "page_sms.h"
#include "page_tuning.h"
#include "pages.h"
#include "read_ini.h"
#include "s_date.h"
#include "s_forms.h"
#include "s_group.h"
#include "visit.h"

using namespace std;
using namespace md5;

// ассоциативные массивы
static std::auto_ptr<A_ARRAY> login_days (new A_ARRAY); // "имя входа/кол-во дней"

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

	iQty = sms->fnGetQty ();
	if (iQty)
		fnGetToday (szToday);
	for (iCycle = 0; iCycle < iQty; iCycle++)
	{
		sms->fnPop ((char *) &s_sms); // разрушающее чтение
		// здесь производится проверка версии записи и при необходимости
		// upgrade (инициализация дополнительных полей)
		if (s_sms.iVersion != SMS_VERSION)
		{
			switch (s_sms.iVersion)
			{
				case 1:
				s_sms.iGate = 0;
			}
			s_sms.iVersion = SMS_VERSION;
		}
		// возвращаем запись в кольцо, если не истёк срок её давности
		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';
		return true;

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

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

	strcpy (sz,szPhones);
	fnCharToComma (sz,'.'); // заменяем точки на запятые
	// размещаем список телефонов в массив
	fnGetLexems (lexems.get (),sz,0);
	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-сообщение
	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;
	// инициализация
	memset (&s_sms,0,sizeof (SMS));
	s_sms.iVersion = SMS_VERSION;
	s_sms.iId = fnGetNextId ();
	s_sms.iGate = 0;
	s_sms.iType = 1; // SMS-уведомление о готовности результатов
	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));
}

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

	if (iClient == -1)
		return;
	// инициализация
	memset (&s_sms,0,sizeof (SMS));
	s_sms.iVersion = SMS_VERSION;
	s_sms.iId = fnGetNextId ();
	s_sms.iGate = 0;
	s_sms.iType = 2; // поздравление с днём рождения
	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;
	// текст сообщения
	strcpy (s_sms.szText,config.szSmsBirthday);
	s_sms.iClientNum = iClientNum; // клиент

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

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

	if (iClient == -1)
		return;
	// инициализация
	memset (&s_sms,0,sizeof (SMS));
	s_sms.iVersion = SMS_VERSION;
	s_sms.iId = fnGetNextId ();
	s_sms.iGate = 0;
	s_sms.iType = 3; // рассылка информации клиентам
	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;
	// текст сообщения
	strcpy (s_sms.szText,szText);
	s_sms.iClientNum = iClientNum; // клиент

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

/*
const int SMS_VERSION = 2;
struct SMS
{
	int iVersion;
	int iId; // идентификатор
	// тип (0 - тест, 1 - выдача результатов, 2 - поздравление с днём
	// рождения)
	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]; // типы 0,1,2; номер телефона получателя в формате 71234567890
	char szText [256 +1]; // типы 0,1,2; текст сообщения в кодировке 1251
	int iClientNum; // тип 1,2; уникальный номер клиента
	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; // типы 0,1,2; идентификатор сообщения
	bool bError; // ошибка, ожидание
	bool bCompleted; // операции завершены
	bool bSuccess; // успешно
	int iGate; // используемый шлюз (0 - не выбран)
};
extern std::auto_ptr<RING> sms;
*/

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

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

	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))
		{
			bContentLenDefined = true;
			iContentLen = atoi (answer->fnGetLine (iCycle)+15);
			break;
		}
	if (! bContentLenDefined)
	{
		delete answer;
		return bReceipt;
	}
	// подсчитываем размер заголовка
	for (iCycle = 0; iCycle < iLinesQty; iCycle++)
	{
		iLineLen = answer->fnGetLineLen (iCycle);
		iHttpLen += iLineLen +2;
		if (! iLineLen) // заголовок принят полностью
		{
			bHttpLenDefined = true;
			break;
		}
	}
	delete answer;
	if (! bHttpLenDefined)
		return bReceipt;
	if ((unsigned int) (iHttpLen+iContentLen) == uiLen)
		bReceipt = true;
	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;
}

void fnSmsAeroQueueExecuteSend (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 szSmsAeroUser [64+3 +1];
	char szSmsAeroSenderName [20 +1];
	char *szInfo;

	switch (s_sms->iStep)
	{
		// **********************
		// * передача сообщения *
		// **********************
		case 0:
		// создаём сокет
		client_socket = socket (AF_INET,SOCK_STREAM,0);
		if (client_socket == 0 || client_socket == INVALID_SOCKET)
		{
			itoa (WSAGetLastError (),szTmp,10);
			s = "fn=fnSmsAeroQueueExecuteSend() code=socket1 msg='socket error' error=";
			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);
		ReleaseSemaphore (hAppSem,1,NULL);
		d_addr = gethostbyname ("gate.smsaero.ru");
//		EnterCriticalSection (&app_cs);
		WaitForSingleObject (hAppSem,INFINITE);
		if (d_addr == NULL)
		{
			closesocket (client_socket);

			s = "fn=fnSmsAeroQueueExecuteSend() 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);
		ReleaseSemaphore (hAppSem,1,NULL);
		iResult = connect (client_socket,(sockaddr *) &dest_addr,sizeof (dest_addr));
//		EnterCriticalSection (&app_cs);
		WaitForSingleObject (hAppSem,INFINITE);
		if (iResult)
		{
			closesocket (client_socket);

			itoa (WSAGetLastError (),szTmp,10);
			s = "fn=fnSmsAeroQueueExecuteSend() code=connect1 msg='connect failed' error=";
			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=fnSmsAeroQueueExecuteSend() 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 (szSmsAeroUser,sizeof (szSmsAeroUser) -1,
			config.szSmsAeroEmail,"@","%40");
		s += szSmsAeroUser;
		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 (szSmsAeroSenderName,sizeof (szSmsAeroSenderName) -1,
			config.szSmsAeroSenderName," ","%20");
		s += szSmsAeroSenderName;
		s += " HTTP/1.1";
		query->fnAddLine (s.c_str ());
		query->fnAddLine ("User-Agent: fnSmsAeroQueueExecuteSend() (Maelstrom WS)");
		query->fnAddLine ("Host: gate.smsaero.ru");
		query->fnAddLine ("");
		chBuf = new char [WS_MAX_BUF_SIZE +1]; // +1 для завершающего нуля
		uiLen = query->fnSend (chBuf,WS_MAX_SEND_SIZE);
		delete query;

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

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

		// дождаться ответа
//		LeaveCriticalSection (&app_cs);
		ReleaseSemaphore (hAppSem,1,NULL);
		uiLen = 0;
		do
		{
			iResult = recv (client_socket,chBuf+uiLen,WS_MAX_BUF_SIZE-uiLen,0);
			if (iResult == 0 || iResult == SOCKET_ERROR)
				break;
			uiLen += iResult;
		} while (! fnCheckReceipt (chBuf,uiLen));
//		EnterCriticalSection (&app_cs);
		WaitForSingleObject (hAppSem,INFINITE);
		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)
		{
			itoa (WSAGetLastError (),szTmp,10);
			s = "fn=fnSmsAeroQueueExecuteSend() code=socket1 msg='socket error' error=";
			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);
		ReleaseSemaphore (hAppSem,1,NULL);
		d_addr = gethostbyname ("gate.smsaero.ru");
//		EnterCriticalSection (&app_cs);
		WaitForSingleObject (hAppSem,INFINITE);
		if (d_addr == NULL)
		{
			closesocket (client_socket);

			s = "fn=fnSmsAeroQueueExecuteSend() 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);
		ReleaseSemaphore (hAppSem,1,NULL);
		iResult = connect (client_socket,(sockaddr *) &dest_addr,sizeof (dest_addr));
//		EnterCriticalSection (&app_cs);
		WaitForSingleObject (hAppSem,INFINITE);
		if (iResult)
		{
			closesocket (client_socket);

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

		// запрос
		query = new VDL_TEXT;
		s = "GET /status/?user=";
		fnReplace (szSmsAeroUser,sizeof (szSmsAeroUser) -1,
			config.szSmsAeroEmail,"@","%40");
		s += szSmsAeroUser;
		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: fnSmsAeroQueueExecuteSend() (Maelstrom WS)");
		query->fnAddLine ("Host: gate.smsaero.ru");
		query->fnAddLine ("");
		chBuf = new char [WS_MAX_BUF_SIZE +1]; // +1 для завершающего нуля
		uiLen = query->fnSend (chBuf,WS_MAX_SEND_SIZE);
		delete query;

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

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

		// дождаться ответа
//		LeaveCriticalSection (&app_cs);
		ReleaseSemaphore (hAppSem,1,NULL);
		uiLen = 0;
		do
		{
			iResult = recv (client_socket,chBuf+uiLen,WS_MAX_BUF_SIZE-uiLen,0);
			if (iResult == 0 || iResult == SOCKET_ERROR)
				break;
			uiLen += iResult;
		} while (! fnCheckReceipt (chBuf,uiLen));
//		EnterCriticalSection (&app_cs);
		WaitForSingleObject (hAppSem,INFINITE);
		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);

		// *******************
		// * контроль шага 3 *
		// *******************
		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

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

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

	// текущее время
	GetLocalTime (&lt);
	SystemTimeToFileTime (&lt,&ft);
	// сканирование очереди
	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;
		}
		try
		{
			if (config.bSmsEnabled)
			{
				switch (config.iSmsGate)
				{
					// ************
					// * SMS Aero *
					// ************
					case SMS_GATE_AERO:
					// SMS-сообщение приписано к другому шлюзу
					if (s_sms.iGate && (s_sms.iGate != SMS_GATE_AERO))
						break;
					// вызов соответствующей функции обработки
					switch (s_sms.iType)
					{
						case 0:
						case 1:
						case 2:
						case 3:
						fnSmsAeroQueueExecuteSend (&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 szDate2 [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 (szDate2); // сегодня
	fnCreateDate (szDate1,szDate2,-1*(iDays -1));

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

	query->dst->fnLoad ("templates/app_help_sms.tpl");

	// выбор периода
	s = "<p><form action=\"/sms_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>\
<td>№</td>\
<td>Дата</td>\
<td>Время</td>\
<td>Тип</td>\
<td>Шаг</td>\
<td>Префикс</td>\
<td>Клиент</td>\
<td>Тел.номер</td>\
<td>Текст сообщения</td>\
<td>Ответ сервиса</td>\
<td>&nbsp;</td>\
<td>№</td>\
<td>&nbsp;</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,szDate2))
			continue;
		// №
		s = "<tr><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/stock_id.png\" title=\"Выдача результатов\" border=0>";
			else
				s += "<img src=\"/images/24x24/stock_id.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;

			default:
			s += "&nbsp;";
		}
		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)
			s += s_sms.szServiceAnswer;
		else
			s += "&nbsp;";
		s += "</td>";
		// статус
		s += "<td valign=top>";
		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>";
			}
		}
		else
			s += "&nbsp;";
		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 = "<p><table border=0>";

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

	// *****************************************
	// * рассылка поздравлений с днём рождения *
	// *****************************************
	s += "<tr><td><form action=\"/sms_birthday_accept\" method=post>";
	s += "Поздравления с днём рождения:";
	query->dst->fnAddLine (s.c_str ());
	fnSelectDate (query,0,szDate2);
	s = "<input type=submit value=\"Разослать\"></form></td></tr>";

	// ********************************
	// * рассылка информации клиентам *
	// ********************************
	s += "<tr><td><form action=\"/sms_info_accept\" method=post>";
	s += "Рассылка информации клиентам: период с";
	query->dst->fnAddLine (s.c_str ());
	// дата 1
	fnSelectDate (query,"from_",szDate2);
	query->dst->fnAddLine ("по");
	// дата 2
	fnSelectDate (query,"to_",szDate2);
	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></tr>";

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

	fnFormEnd (query);
}

void fnAppSmsAccept (QUERY *query)
{
	RING *args = query->args;
	char sz [INI_MAX_LEXEM_LEN +1];
	int iDays = 7;
	char szTmp [20];

	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 fnAppSmsEdit (QUERY *query)
{
	RING *args = query->args;
	char sz [INI_MAX_LEXEM_LEN +1];
	int iSmsId = 0;
	int iQty;
	int iCycle;
	SMS s_sms;
	bool bFound = false;
	string s;
	char szTmp [20];
	int iClient;
	char szClientFIO [100]; // для вывода FIO

	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>";
	// №
	s += "<tr><td>№:</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>";
	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:
		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>";
	// в скрытых параметрах 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)
{
	RING *args = query->args;
	char sz [INI_MAX_LEXEM_LEN +1];
	bool bRestart = false;
	bool bStop = false;
	bool bNothingToDo = true; // нечего делать
	int iSmsId = 0;
	int iQty;
	int iCycle;
	SMS s_sms;

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

	iQty = sms->fnGetQty ();
	for (iCycle = 0; iCycle < iQty; iCycle++)
	{
		sms->fnPop ((char *) &s_sms,iCycle);
		if (s_sms.iId == iSmsId)
		{
			if (bRestart)
			{
				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)
			{
				s_sms.bCompleted = true;
			}
			sms->fnReplace ((char *) &s_sms,iCycle,sizeof (SMS));
			break;
		}
	}
}

void fnAppSmsTestAccept (QUERY *query)
{
	RING *args = query->args;
	char sz [INI_MAX_LEXEM_LEN +1];
	char szFederalPhone [11 +1];
	bool bPhoneChecked = false;
	char szText [256 +1] = "";
	int iAction = 0;

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

	// проверяем, выполнена ли транзакция
	if (actions->fnCheck (iAction))
		return;
	if (! bPhoneChecked)
		return;
	if (! strlen (szText))
		return;
	// помещаем SMS-сообщение в очередь для отправки
	fnSendSmsTest (query,szFederalPhone,szText);
}

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)
{
	char sz [INI_MAX_LEXEM_LEN +1];
	int iDay = 0;
	int iMonth = 0;
	RING *args = query->args;
	int iCycle;
	int iClientNum;

	while (args->fnGetQty ())
	{
		args->fnPop (sz);
		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;
		}
	}

	// цикл с единицы, т.к. нулевой клиент - Анонимный
	for (iCycle = 1; iCycle < iClientsQty; iCycle++)
	{
		// пропускаем удалённые записи
		if (client [iCycle]->fnGetDelete ())
			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
		fnSendSmsBirthday (query,iClientNum);
	}
}

void fnAppSmsInfoAccept (QUERY *query)
{
	RING *args = query->args;
	char sz [INI_MAX_LEXEM_LEN +1];
	int iDay1 = 0;
	int iMonth1 = 0;
	int iYear1 = 0;
	int iDay2 = 0;
	int iMonth2 = 0;
	int iYear2 = 0;
	char szDate1 [11];
	char szDate2 [11];
	char szGroupId [2 +1];
	int iGroupId = 0;
	char szText [256 +1] = "";
	int iAction = 0;
	int iCycle;
	int iClientNum;
	std::auto_ptr<A_KEY_INT> clients_nums (new A_KEY_INT (16 kb));
	int iQty;

	while (args->fnGetQty ())
	{
		args->fnPop (sz);
		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,"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;
		}
		if (! strcmp (sz,"group"))
		{
			args->fnPop (szGroupId);
			iGroupId = atoi (szGroupId);
			continue;
		}
		if (! strcmp (sz,"sms_text"))
		{
			args->fnPop (szText);
			continue;
		}
		if (! strcmp (sz,"action"))
		{
			args->fnPop (sz);
			iAction = 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]->fnGetClientNum()) // анонимный клиент
			continue;
		if (! fnCheckDateRange (szDate1,visit [iCycle]->fnGetDate (),szDate2))
			continue;
		if (visit [iCycle]->fnGetPreEntry ())
			continue;
		// группа
		if (iGroupId >= 0)
			if (! fnServicesGroupsGetMember (visit [iCycle]->fnGetType (),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 (16 kb);
		clients_nums->fnCopyBuf (buf);
		for (iCycle = 0; iCycle < iQty; iCycle++)
		{
			buf->fnPop((char *) &iClientNum,iCycle);
			iClient = fnClientSearchByNum(iClientNum);
			if (iClient == -1)
				continue;
			// пропускаем удалённые записи
			if (client [iCycle]->fnGetDelete ())
				continue;

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

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