Модуль UG7_Total.cpp является частью функционала программы Gt2crm3 (которая предназначена для формирования текстового файла звонков, необходимого для загрузки данных в систему CRM3 (звонковое хранилище)).
Модуль выполняет проверку сформированных данных, то есть:
а) производит считывание данных из сформированного файла,
б) суммирует данные по оператору, направлению доходности и направлению трафика (и по коду платежа, если нужно),
в) сохраняет итоги в файле и/или в БД (в зависимости от конфигурации).
Данная проверка позволяет оперативно реагировать на проблемы, если со стороны потребителей данных (системы crm3) возникнут претензии о том, что данные искажены или недостоверны. В этом случае есть возможность быстро выполнить сверку данных (без обращения к звонковому файлу, содержащего миллионы строк данных) и в приемлемые сроки обнаружить ошибку, в какой бы системе и по какой бы причине она не возникла.
Алгоритмически задача проверки выполняется с помощью контейнера map STL:
typedef map<string, TGood> goods;
Здесь string представляет собой ключ (который содержит в качестве составных частей код оператора, направление доходности и трафика), а TGood представляет собой класс, содержащий итоги (количество звонков, общая длительность, сумма в валюте счета и в долларовом эквиваленте).
class TGood {
private:
int FCalls;
double FDuration;
double FRub;
double FUsd;
public:
__fastcall TGood();
__fastcall TGood(int calls, double duration, double rub, double usd);
__fastcall TGood(const TGood &X);
TGood & __fastcall operator =(const TGood &X);
TGood & __fastcall operator +=(const TGood &X);
__property int Calls = { read = FCalls };
__property double Duration = { read = FDuration };
__property double Rub = { read = FRub };
__property double Usd = { read = FUsd };
};
Работа с контейнером goods ведется с помощью следующего класса (показаны основные методы для работы с контейнером):
class TTotals {
private:
goods FGoods;
// остальное удалено
public:
__fastcall TTotals();
// остальное удалено
void __fastcall Add(
const char *Scala, const char *InvDir, const char *Dir,
const char *CallDate, const char *PayCode,
int calls, double duration, double rub, double usd
);
void __fastcall Print(FILE *f);
};
В методе TTotals.Add() происходит заполнение контейнера:
void __fastcall TTotals::Add(const char *Scala, const char *InvDir,
const char *Dir, const char *CallDate, const char *PayCode,
int calls, double duration, double rub, double usd)
{
TGood G(calls, duration, rub, usd);
char buf[32];
char bfA[8];
char SDir[2], SInvDir[2];
char code;
string key;
if(InvDir[0] == '0') {
SInvDir[0] = 'T';
} else {
SInvDir[0] = 'F';
}
SInvDir[1] = '\0';
if(Dir[0] == '0') {
SDir[0] = 'I';
} else {
SDir[0] = 'O';
}
SDir[1] = '\0';
if(FTstPayCodes) {
sprintf(buf, "%s;%s;%s;%s;%s", Scala, SInvDir, SDir, CallDate, PayCode);
key = buf;
} else {
code = (CallDate[0] - '0') * 10 + (CallDate[1] - '0');
code |= ((InvDir[0] - '0') << 5) & 0x20;
code |= ((Dir[0] - '0') << 6) & 0x40;
sprintf(bfA, "%c%s", code, Scala);
key = bfA;
}
goods::iterator p = FGoods.find(key);
if (p == FGoods.end()) {
// Нет узла, добавляем
FGoods[key] = G;
} else {
// Есть узел, модифицируем
FGoods[key] += G;
}
}
Метод TTotals::Print() содержит код использования контейнера:
void __fastcall TTotals::Print(FILE *f)
{
char SMin[32], SAmt[32], SUsd[32];
string value;
goods::iterator p;
int calls;
double duration;
double rub;
double usd;
char buf[128];
char code, InvDir, Dir;
int D;
TGood G;
for (p = FGoods.begin(); p != FGoods.end(); ++p) {
value = p->first;
G = p->second;
sprintf(buf, "%s", value);
GetFloatStr(SMin, G.Duration/60);
GetFloatStr(SAmt, G.Rub);
GetFloatStr(SUsd, G.Usd);
calls = G.Calls;
duration = G.Duration;
rub = G.Rub;
if(FTstPayCodes) {
fprintf(f, "%s;%d;%s;%s;%s\n",
buf, calls, SMin, SAmt, SUsd );
} else {
code = buf[0];
D = code & 0x1F;
InvDir = (code & 0x20) >> 5;
if(InvDir == 0) { InvDir = 'T'; } else { InvDir = 'F'; }
Dir = (code & 0x40) >> 6;
if(Dir == 0) { Dir = 'I'; } else { Dir = 'O'; }
fprintf(f, "%s;%c;%c;%02d;%d;%s;%s;%s\n",
&buf[1], InvDir, Dir, D, calls, SMin, SAmt, SUsd );
}
}
}
Процесс проверки результирующего файла выполняется в классе TG7_Tst:
class TG7_Tst : public TG7_Core
{
private:
TTotals FTotals;
void __fastcall TstBody();
bool __fastcall TstFile(AnsiString Fn);
void __fastcall TstOne(char *s);
// остальное удалено
public:
__fastcall TG7_Tst();
__fastcall ~TG7_Tst();
// остальное удалено
};
Ниже приводится код функции TstOne(), в которой производится разбор одной строки файла-результата:
/* Тестирование одной строчки из файла-результата.
*/
void __fastcall TG7_Tst::TstOne(char *s)
{
int i = 0;
char Scala[6], CallDate[24], PayCode[64], RevCost[2], Dir[2], RealDur[24];
char Amt[24], AmtUsd[24];
double FRealDur, FAmt, FAmtUsd;
ParseStr(s, Scala, 2, ',', '\'', i);
if(Scala[0] != '\0') {
ParseStr(s, CallDate, 1, ',', '\'', i);
CallDate[10] = '\0';
ParseStr(s, PayCode, 13, ',', '\'', i);
ParseStr(s, RevCost, 15, ',', '\'', i);
ParseStr(s, Dir, 16, ',', '\'', i);
ParseStr(s, RealDur, 5, ',', '\'', i);
ParseStr(s, Amt, 9, ',', '\'', i);
ParseStr(s, AmtUsd, 8, ',', '\'', i);
FRealDur = atof(RealDur);
FAmt = atof(Amt);
FAmtUsd = atof(AmtUsd);
FTotals.Add(
Scala, RevCost, Dir, &CallDate[8], PayCode,
1, FRealDur, FAmt, FAmtUsd
);
}
}
Здесь производится распарсивание строки на нужные токены (с помощью функции ParseStr(), здесь не показана), формирование переменных и вызов метода FTotals.Add для заполнения контейнера с итогами разбора.
Ниже приводится код функции для тестирования файла:
#define BUF_SIZE 1024*1024
/* Тестирование файла
*/
bool __fastcall TG7_Tst::TstFile(AnsiString Fn)
{
char *buf;
char sbuf[256];
int FIn;
int BufCnt, BufPtr = 0, SPtr = 0;
if((buf = (char *) calloc(1, BUF_SIZE)) == NULL) {
CErrLog = "Ошибка выделения буфера";
CStat = errERR;
goto L1;
}
if((FIn = FileOpen(Fn.c_str(), fmOpenRead | fmShareDenyWrite)) == -1) {
CErrLog = Format("Ошибка открытия файла %s", ARRAYOFCONST((Fn)));
CStat = errERR;
goto L2;
}
CMsgLog = Format("Начало тестирования файла %s", ARRAYOFCONST((Fn)));
while( (BufCnt = FileRead(FIn, buf, BUF_SIZE)) > 0) {
for( BufPtr = 0; BufPtr < BufCnt; BufPtr++) {
if(buf[BufPtr] != '\n') {
sbuf[SPtr++] = buf[BufPtr];
} else {
sbuf[SPtr] = '\0';
if(FTstDoIt) {
TstOne(sbuf);
}
SPtr = 0;
}
}
memset(buf, '\0', BUF_SIZE);
}
FileClose(FIn);
L2:
// освобождаем буфер
try {
free(buf);
} catch (Exception &exc) {
CErrLog = Format("Ошибка освобождения буфера: %s",
ARRAYOFCONST((
exc.Message
)));
}
L1:
CMsgLog = Format("Окончание тестирования файла %s", ARRAYOFCONST((Fn)));
return (CStat == OK);
}
В функции происходит выделение памяти под буфер, далее открывается файл для разбора, из файла происходит блочное считывание, после чего осуществляется вызов метода TstOne для каждой строки, находящейся в буфере.
В заключение приведен код TG7_Tst::TstBody(), в котором и осуществляется процесс проверки файла-результата:
/* Код процесса.
*/
void __fastcall TG7_Tst::TstBody()
{
AnsiString S, File_to, DayStr;
AnsiString File_det;
int MIdx = 0, RCidx, fHndl_out, Day, j, i;
char ToDurStr[32], ToMinStr[32], ToAmtStr[32], ToUsdStr[32];
char FrDurStr[32], FrMinStr[32], FrAmtStr[32], FrUsdStr[32];
bool found, created = false;
FILE *FOut, *FDet;
DayStr = CCallDate.FormatString("dd");
if(FTstDet) {
File_det = Format("%s%s.det", ARRAYOFCONST((
CTotDir, CCallDate.FormatString("yyyymmdd") )));
if((FDet = fopen(File_det.c_str(), "a")) == NULL) {
CErrLog = Format("Ошибка открытия файла %s",
ARRAYOFCONST((File_det
)));
return ;
}
}
FTotals.TstPayCodes = FTstPayCodes;
FTotals.TstInsViaProc = FTstInsViaProc;
FTotals.Day = FDay;
FTotals.ExpID = CExpID;
FTotals.CommitRows = CCommitRows;
TstDay(); // тестирование данных за день (может быть несколько файлов)
if(FTstMakeCsv) {
File_to = Format("%s", ARRAYOFCONST(( FTarget_tot )));
if( FileExists(File_to) == false ) {
CMsgLog = Format("Файл %s не существует. Создаю",
ARRAYOFCONST((File_to
)));
created = true;
}
if((FOut = fopen(File_to.c_str(), "a")) == NULL) {
CErrLog = Format("Ошибка открытия файла %s", ARRAYOFCONST((File_to)));
} else {
if(created) {
FTotals.PrintHead(FOut);
}
FTotals.Print(FOut);
fclose(FOut);
}
}
if(FTstSaveRez) {
CMsgLog = "Сохранение результатов тестирования в БД";
if(FTotals.InsIntoRez(COra, CCallDate.FormatString("dd").ToIntDef(1))) {
CMsgLog = "Результаты тестирования сохранены в БД";
} else {
CErrLog = "Ошибка при сохранении результатов тестирования";
}
}
}
Как видно, TstBody() делегирует процесс тестирования функции TstDay(), которая осуществляет вызовы функций TstFile() (на этой странице не показано), где происходит заполнение контейнера с результатами тестирования.
После чего происходит формирования файла с результатами тестирования (через вызов FTotals.Print) либо сохранение результатов в БД (на этой странице не показано).