//---------------------------------------------------------------------------


#pragma hdrstop

#include "PrinterOPOS.h"
#include "OposFiscalPrinter_1_7_Lib_OCX.h"
#include "ud_Module.h"
#include <Registry.hpp>
#include <Math.h>
#include <DateUtils.hpp>
#include <Dialogs.hpp>
//---------------------------------------------------------------------------

#pragma package(smart_init)
//---------------------------------------------------------------------------
PrinterOPOS* printerOPOS;
//---------------------------------------------------------------------------
char * kkm_err_msg_ru[] = {
"OK",								//0
"ФП ID ошибка",						//1	FM ID Error
"Ошибка записи в ФП ",				//2	FM Rec Error
"Flash Chip не может быть очищен",	//3 Flash Chip cant clear
"ФП не пуста",						//4	FM not empty
"Данные в ФП разрушенны",			//5	FM Data destroyed
"Реж принтер игнорир кмд",			//6	Cmd ignor prn mode
"Нет кода оператора",				//7	Oper code is absent
"Нет типа документа",				//8	Doc type is absent
"Сер. номер ККМ",					//9 Ser num CR
"Ошибка пароля",					//10 Password error
"Ошибка дата/время",				//11 Data/Time error
"Завод. рег. ранее",				//12 Factory registration has already pass
"Сер ном ККМ разрушен",				//13 Ser num CR destroyed
"Фискализация ранее",				//14 Fiscalize has already pass
"ККМ не фиск режим",				//15 Not fisc mode CR
"Фискал ККМ разрушен",				//16 Fiscalize Data destroyed
"Перерег ККМ исчерпан",				//17 Rewrite is conclude
"Перерег ККМ разрушен",				//18 Rewrite is destroyed
"Смена открыта",					//19 Shift already open
"См не была открыта",				//20 Shift not opened
"Много товара в чеке",				//21 Many goods in receipt
"СМ в ФП разрушенна",				//22 EOD data in FM destroyed
"Формат документа",					//23 Document format error
"Перезапись текста",				//24 Text rewrite
"Много пар-ров док",				//25 Many parameters in document
"Ош параметр док-та",				//26 Document parameters error
"Ош значения пар-ра",				//27 Parameters value error
"Несуществующ объект",				//28 Object non-existent
"Док не был завершен",				//29 Document is not finished
"Товар/опер не полно",				//30 goods/operation description not enough
"Контрол сумма пакета",				//31 Check sum packet
"Принтер не готов",					//32 Prn not ready
"Вложенные прерывания",				//33 Nested interrupts
"Байт подтв прием данных",			//34 Check Sum Error
"Переполненние в ФП",				//35 Overflow if FM
"Неизвестная комманда",				//36 Unknown command
"CRC ОЗУ",							//37 CRC RAM
"CRC ФП",							//38 CRC FM
"Порт данных неисправ",				//39 Data port defective
"Порядок рекв чека",				//40 Receipts parameters sequence
"0xy для обязат рекв",				//41 0xy for required properties
"Длинна запроса",					//42 Length request
"Разрядность числа",				//43 number capacity
"Переполнение",						//44 Overflow
"Нет числа",						//45 Number is absent
"Нет ответа",						//46 Not response
"Ошибка передачи",					//47 Transmission error
"Мат. переполнение",				//48 Math overflow
"Нет бумаги...",					//49 Paper out...
"ККМ не готова!!!",					//50 CR not ready!!!
"Сбой памяти!!",					//51 Memory error!!
"Принтер выключен",					//52 Printer off
"Недостаточно памяти",				//53 Insufficient memory
"Длинна номера меньше",				//54 Lenght number less
"Нулевая длинна номера",			//55 Zero lenght number
"Длинна пароля меньше допустимой",	//56 Password lenght less permissible
"Нулевая длинна пароля",			//57 Zero lenght password
"Недопустимая величина",			//58 Impossible value
"Превышен тайм-аут",				//59 Time-out overdraw
"",
"Ош.ЭКЛЗ",
"Недопустимое состояние ЭКЛЗ",
"Данные учтены",
"","","","","","",//69
"","","","","","","","","","",//79
"","","","","","","","","","",//89
"","","","","","","","","","",//99
"","","","","","","","","","",//109
"","","","","","","","","","",//119
"","","","","","","","","","",//129
"","","","","","","","","","",//139
"","","","","","","","","","",//149
"","","","","","","","","","",//159

"","","","","","","","","","",//169
"","","","","","","","","","",//179
"","","","","","","","","","",//189
"","","","","","","","","","",//199
"","","","","","","","","","",//209
"","","","","","","","","","",//219
"","","","","","","","","","",//229
"","","","","","","","","","",//239
"Команды нет",//240									Cmd not init
"Ош. посылки команды",//241							Cmd write err
"Команда не отослана ККМ-неготов(DSR=0)",//242					Prn not ready (DSR=0)
"Команда отослана, но ответ что ККМ-неготов",//243		Cmd ok
"Превышено время ожидания ответа",//244					Cmd resp timeout
"Ошибка",//245										Prn error
"Пришло больше данных, чем ожидали",//246			Cmd resp large
"Пришло меньше данных, чем ожидали",//247			Cmd resp small
"Пришли некие данные",//248
"Порт закрыт",//249
"Порт закрыт во время выполнения",//250				Cmd break
"ККМ выключен или сломан",//251
"ККМ неготов",//252
"Ошибка в параметрах команды",//253
"Несуществующий номер команды",//254
"ККМ занят(предыдущая команда)!"//255			Previous cmd run
};
//---------------------------------------------------------------------------
PrinterOPOS::PrinterOPOS(TComponent* AOwner)
{
	Crashed=false;
	fp=new TOPOSFiscalPrinter(AOwner);
	fp->DeviceEnabled = False;
	FListGoods= new TList();
	LoadSetup();
}
//---------------------------------------------------------------------------
PrinterOPOS::~PrinterOPOS()
{
	delete FListGoods;
	delete fp;
}
//---------------------------------------------------------------------------
void PrinterOPOS::LoadSetup()
{
	TIniFile* ini=new TIniFile(ExtractFilePath(ParamStr(0))+"\\Setup.ini");
//	cPort->port=ini->ReadString("Printer","Port","");
//	cPort->BaudRate=ini->ReadInteger("Printer","BaudRate",0);
	CheckId=ini->ReadInteger("Printer","CheckCount",0);
	TestMode=ini->ReadBool("Printer","TestMode",true);
	HeadLine=ini->ReadString("Printer","HeadLine","");
	delete ini;
}
//---------------------------------------------------------------------------
void PrinterOPOS::SaveSetup()
{
	TIniFile* ini=new TIniFile(ExtractFilePath(ParamStr(0))+"\\Setup.ini");
//	ini->WriteString("Printer","Port",cPort->port);
//	ini->WriteInteger("Printer","BaudRate",cPort->BaudRate);
	ini->WriteInteger("Printer","CheckCount",CheckId);
	ini->WriteBool("Printer","TestMode",CheckId);
	ini->WriteString("Printer","HeadLine",HeadLine);
	delete ini;
}
//---------------------------------------------------------------------------
bool PrinterOPOS::Start() //Соедениться по COM порту и проинициализировать принтер
{
	if(fp->DeviceEnabled) return true;

	FState = fp->Open(L"MbsFiscalPrinter");
	if(FState!=0)
	{
		saveLog(IntToStr(FState)+"(PrinterOPOS::Start)"+GetStatusDescription(FState));
		return false;
	}
	FState = fp->ClaimDevice(1000);
	if(FState!=0)
	{
		saveLog(IntToStr(FState)+"(PrinterOPOS::Start)"+GetStatusDescription(FState));
		return false;
	}
	fp->DeviceEnabled = True;
	return true;
}
//---------------------------------------------------------------------------
void PrinterOPOS::setHeader(TStrings* Text)
{
	//if(fp->DayOpened){FState=19; return;}

	long err;
	for(int i=0;i<Text->Count;i++)
	{
		WideString str=Text->Strings[i];
		err=fp->SetHeaderLine(i+1,str,false);
		if(err!=0)
		{
			saveLog(IntToStr(err)+"(PrinterOPOS::setHeader)"+GetStatusDescription(err));
			break;
		}
	}
	err=fp->ResetPrinter();
	if(err!=0) saveLog(IntToStr(err)+"(PrinterOPOS::setHeader)"+GetStatusDescription(err));
}
//---------------------------------------------------------------------------
void PrinterOPOS::getHeader(TStrings* Text)
{
	TIniFile* ini=new TIniFile(ExtractFilePath(ParamStr(0))+"\\Setup.ini");
	Text->Add(ini->ReadString("Printer","HeadLine1",""));
	Text->Add(ini->ReadString("Printer","HeadLine2",""));
	Text->Add(ini->ReadString("Printer","HeadLine3",""));
	Text->Add(ini->ReadString("Printer","HeadLine4",""));
	Text->Add(ini->ReadString("Printer","HeadLine5",""));
	delete ini;

	for(int i=Text->Count-1;i>=0;i--)
		if(Text->Strings[i]=="")Text->Delete(i); else break;
}
//---------------------------------------------------------------------------
void PrinterOPOS::setTrailer(TStrings* Text)
{
	//if(fp->DayOpened){FState=19; return;}
	long err;
	for(int i=0;i<Text->Count;i++)
	{
		WideString str=Text->Strings[i];
		err=fp->SetTrailerLine(i+1,str,false);
		if(err!=0)
		{	saveLog(IntToStr(err)+"(PrinterOPOS::setTrailer)"+GetStatusDescription(err));
			break;
		}
	}
	err=fp->ResetPrinter();
	if(err!=0) saveLog(IntToStr(err)+"(PrinterOPOS::setTrailer)"+GetStatusDescription(err));
}
//---------------------------------------------------------------------------
void PrinterOPOS::getTrailer(TStrings* Text)
{
	TIniFile* ini=new TIniFile(ExtractFilePath(ParamStr(0))+"\\Setup.ini");
	Text->Add(ini->ReadString("Printer","TailLine1",""));
	Text->Add(ini->ReadString("Printer","TailLine2",""));
	Text->Add(ini->ReadString("Printer","TailLine3",""));
	Text->Add(ini->ReadString("Printer","TailLine4",""));
	Text->Add(ini->ReadString("Printer","TailLine5",""));
	delete ini;

	for(int i=Text->Count-1;i>=0;i--)
		if(Text->Strings[i]=="")Text->Delete(i); else break;
}
//---------------------------------------------------------------------------
void PrinterOPOS::addGoods(Goods* goods)
{
	FListGoods->Add(goods);
}
//---------------------------------------------------------------------------
void PrinterOPOS::ClearGoods()
{
	try
	{
		while(FListGoods->Count>0)
		{
			Goods* g=(Goods*)FListGoods->Items[0];
			delete g;
			FListGoods->Delete(0);
		}
		FListGoods->Clear();
	}catch(...)
	{
		Crashed=true;
	}
}
//---------------------------------------------------------------------------
int PrinterOPOS::getGoodsPrice()
{
	int sum=0;
	for(int i=0;i<FListGoods->Count;i++)
		sum+=((Goods*)FListGoods->Items[i])->price;
	return sum;
}
//------------------------------------------------------------------------------
int PrinterOPOS::getNextCheckId()
{
	CheckId++; //Номер чека
	SaveSetup(); //сохраняем номер чека
	return CheckId;
}
//------------------------------------------------------------------------------
bool PrinterOPOS::SaveCheck(int CheckId) //Сохранение чека для последующей распечатки (суммы)
{
	if(dModule->UserType) //true - Юредическое лицо; false - физическое лицо
		dModule->SaveCheck(CheckId,dModule->UserId,0,getGoodsPrice());
	else
		dModule->SaveCheck(CheckId,0,dModule->UserId,getGoodsPrice());
}
//---------------------------------------------------------------------------
bool PrinterOPOS::deleteCheck(int CheckId) //Удаление чека
{
	dModule->UpdateChek(CheckId);
}
//---------------------------------------------------------------------------
bool PrinterOPOS::PrintLine(WideString line)
{
	//Не фискальная печать
	long err = fp->PrintNormal(2, WideString(WCHAR(0x1B)) + WideString(WCHAR(0x7C)) + line + L"\n");
	return err==0;
}
//---------------------------------------------------------------------------
//sales - true продажа, false - возврат
//check - номер чека если не задан =0 то генериться
//TODO Фича это фиксировать не распечатанные чеки потом печатать их
bool PrinterOPOS::PrintCheck(bool sales, int check) //Тестовая печать
{
	long err=0;
	Currency cur=0;
	Currency cAllPrice=0;
	bool bOk=true;

	try
	{
		//Считаем сумму в чеке (заранее на случай ошибки)
		cAllPrice=getGoodsPrice();

		if(check==0) //если не задан номер чека то генерим сдесь
		{
			CheckId++; //Номер чека
			check=CheckId;
			SaveSetup(); //сохраняем номер чека
		}

		if(fp->RecEmpty) throw Exception("Нет бумаги!");
		if(!DayOpened())
			if(!OpenShift()) throw Exception("Не удалось открыть смену");

		Currency UnitPrice,AllPriceDrb,AllPrice;
		long Qnt,vat;
		BSTR* bstr;
		WideString sStr;
		BSTR sTmp=new OLECHAR[255];
		sTmp=L"";
		long MbsResponceCode;
		CURRENCY Amount1,Amount2;

		//Установка номера отдела
		//vat=11; sTmp=L"отдел";
		//err = fp->DirectIO(4, &vat, &sTmp);
		//if (err) goto ErrHandler;

		if(sales) fp->FiscalReceiptType = FPTR_RT_SALES; //Чек продажи
		else fp->FiscalReceiptType=FPTR_RT_SERVICE; //Чек возврата

		//Фискальная печать (с заголовком или без)
		err = fp->BeginFiscalReceipt(true);
		if (err) throw Exception(getLastError(&err)); //251

		//Товары из списка в драйвер
		for(int i=0;i<FListGoods->Count;i++)
		{
			Goods* goods=(Goods*)FListGoods->Items[i];
			VarCyFromR8(goods->price*100,&Amount1);
			Amount2.int64=0;
			err=fp->PrintRecItem(goods->name+L"-/--/-0", Amount1, goods->count, goods->VatInfo, Amount2, L"Шт");
			if (err) throw Exception(getLastError(&err));
		}
		ClearGoods();

		//установка номера чека (Перед PrintRecTotal)
		err = fp->DirectIO(3, &CheckId, &sTmp);
		if (err) throw Exception(getLastError(&err));

		//Установка значений массива налоговых ставок
		//setNalog(1,12);

		//Формирования итога в чеке
		Amount2.int64=0;
		VarCyFromR8(cAllPrice*100,&Amount1);
		err=fp->PrintRecTotal(Amount1, Amount1, L"na");
		if (err) throw Exception(getLastError(&err));


		err = fp->EndFiscalReceipt(True); //Окончания печати, в нем происходит печать дополнительного трейлера (AdditionalTrailer) и обрезка бумаги
		if(err) throw Exception(getLastError(&err));
	}
	catch(Exception &exception)
	{
		printerOPOS->ClearGoods();
		if(err==251) Crashed=true; //Кабздец
		saveLog("Ошибка при печати чека: N: "+IntToStr(dModule->UserId)+" сумма: "+CurrToStr(cAllPrice)+" Чек: "+IntToStr(CheckId)+". "+exception.Message);
		dModule->SendError(err,"Ошибка при печати чека: N: "+IntToStr(dModule->UserId)+" сумма: "+IntToStr(cAllPrice)+" Чек: "+IntToStr(CheckId)+". "+exception.Message);
		bOk=false;
	}
	catch(...)
	{
		printerOPOS->ClearGoods();
		if(err==251) Crashed=true; //Кабздец
		saveLog("Не опознанная ошибка при печати чека: N: "+IntToStr(dModule->UserId)+" сумма: "+IntToStr(cAllPrice)+" Чек: "+IntToStr(CheckId));
		dModule->SendError(1,"Не опознанная ошибка при печати чека: N: "+IntToStr(dModule->UserId)+" сумма: "+IntToStr(cAllPrice)+" Чек: "+IntToStr(CheckId));
		bOk=false;
	}
	//сброс чека
	if(!bOk)
	{
		fp->PrintRecVoid(L"");
	}

	return bOk;
}
//---------------------------------------------------------------------------
void PrinterOPOS::Cut() //Отрезать бумагу
{
	FState = fp->PrintNormal(2, WideString(WCHAR(0x1B)) + WideString(WCHAR(0x7C)) + "75P"); //обрезка бумаги
	if(FState!=0) saveLog(IntToStr(FState)+"(PrinterOPOS::Cut)"+GetStatusDescription(FState));
}
//---------------------------------------------------------------------------
//Получить текущее состояние принтера (чтоб проверить готовность печати)
int PrinterOPOS::GetState()
{
	if(!fp->DeviceEnabled)FState=245;
	//if(FState!=0) return FState;
	//проверяем
	//Надо добавить лог
//	fp->ResultCodeExtended
	return 0;
}
//---------------------------------------------------------------------------
//Проверка роботоспособности принтера
bool PrinterOPOS::isOk()
{
	long err=0;
	try
	{
		if(Crashed) throw Exception("Драйвер принтера разрушен!");
		if(fp->RecEmpty) throw "Нет бумаги!";

		AnsiString str;
		if(!DayOpened())
			OpenShift();
		if(DayOpened())
		{
			//Проверка на длину смены
			TDateTime dt=getDateOpenShift();
			str=getLastError(&err);
			if(err) throw Exception(getLastError(&err));

			dModule->getDate();
			if(HoursBetween(dt,dModule->date)>24) //Разница не более 24 часов
				throw Exception("[1]Смена не закрыта");
		} else throw Exception("Принтер не работает!");
		BSTR bstr=new OLECHAR[50];
		err=fp->GetDate(&bstr);
		if(err) throw Exception(getLastError(&err));
	}
	catch(Exception &exception)
	{
		if(err==251) Crashed=true; //Кабздец
		saveLog("(PrinterOPOS::isOk)"+exception.Message);
		dModule->SendError(1,"(PrinterOPOS::isOk)"+exception.Message);
		return false;
	}
	catch (...)
	{
		if(err==251) Crashed=true; //Кабздец
		saveLog("(PrinterOPOS::isOk)Неопознанная ошибка");
		dModule->SendError(1,"(PrinterOPOS::isOk)Неопознанная ошибка");
		return false;
	}
	return true;
}
//---------------------------------------------------------------------------
//Получить дату открытия смены
TDateTime PrinterOPOS::getDateOpenShift()
{
	long iTmp,err;
	BSTR sTmp=new OLECHAR[50];
	err = fp->DirectIO(13, &iTmp, &sTmp);
	if(err!=0) return 0;
	return StrToDateTime(sTmp);
}
//---------------------------------------------------------------------------
//Описание статуса
AnsiString PrinterOPOS::GetStatusDescription(long err)
{
	AnsiString str;
	try
	{
		if(err<256)
			str=kkm_err_msg_ru[err];
		if(str=="") str="Нет описания ошибки";
	}catch(...)
	{
		saveLog("(PrinterOPOS::GetStatusDescription) Нет описания ошибки");
		return "Нет описания ошибки";
	}
	return str;
}
//---------------------------------------------------------------------------
//Получить код последней ошибки
AnsiString PrinterOPOS::getLastError(long* err)
{
	BSTR str=new OLECHAR[255];
	str=L"Нет описания ошибки!";
	try
	{
		fp->DirectIO(1, err, &str);
	}catch(...)
	{
		saveLog("(PrinterOPOS::getLastError) Нет описания ошибки");
	}
	if(str==L"") return GetStatusDescription(*err);
	return str;
}
//---------------------------------------------------------------------------
AnsiString PrinterOPOS::getLastError()
{
	long err;
	return getLastError(&err);
}
//---------------------------------------------------------------------------
int PrinterOPOS::getCOMPort()
{
	int n=-1;

	TRegistry* reg=new TRegistry;
	reg->RootKey = HKEY_LOCAL_MACHINE;
	if(reg->OpenKeyReadOnly("\\SOFTWARE\\OLEforRetail\\ServiceOPOS\\FiscalPrinter\\MbsFiscalPrinter"))
	{
		n = reg->ReadInteger("COM Num");
	}
	delete reg;
	return n;
}
//---------------------------------------------------------------------------
void PrinterOPOS::setCOMPort(int n)
{
	TRegistry* reg=new TRegistry;
	reg->RootKey = HKEY_LOCAL_MACHINE;
	if(reg->OpenKey("\\SOFTWARE\\OLEforRetail\\ServiceOPOS\\FiscalPrinter\\MbsFiscalPrinter",true))
	{
		reg->WriteInteger("COM Num",n);
	}
	delete reg;
}
//---------------------------------------------------------------------------
int PrinterOPOS::getBaudRate()
{
	int n=-1;

	TRegistry* reg=new TRegistry;
	reg->RootKey = HKEY_LOCAL_MACHINE;
	if(reg->OpenKeyReadOnly("\\SOFTWARE\\OLEforRetail\\ServiceOPOS\\FiscalPrinter\\MbsFiscalPrinter"))
	{
		n = reg->ReadInteger("COM Speed");
	}
	delete reg;
	return n;
}
//---------------------------------------------------------------------------
void PrinterOPOS::setBaudRate(int val)
{
	TRegistry* reg=new TRegistry;
	reg->RootKey = HKEY_LOCAL_MACHINE;
	if(reg->OpenKey("\\SOFTWARE\\OLEforRetail\\ServiceOPOS\\FiscalPrinter\\MbsFiscalPrinter",true))
	{
		reg->WriteInteger("COM Speed",val);
	}
	delete reg;
}
//---------------------------------------------------------------------------
//Печать отчёта и закрытие смены
bool PrinterOPOS::PrintZReport()
{
	int sum=getDaySum();
	long err=fp->PrintZReport();
	if(err==0)
	{
		dModule->addZReport(sum);//Послать на сервер время снятия Z отчёта
		dModule->addPaper(-142*2);//Для подсчёта конца бумаги
	}else
	{
		saveLog("Ошибка печати Z отчёта");
		dModule->SendError(1,"Ошибка печати Z отчёта");
	}
	return err==0;
}
//---------------------------------------------------------------------------
//Печать суточного отчёта без закрытия смены
void PrinterOPOS::PrintXReport()
{
	fp->PrintXReport();
}
//---------------------------------------------------------------------------
//Открытие смены
bool PrinterOPOS::OpenShift()
{
	long n=1;
	BSTR str=L"Платёжный терминал 1";
	long err=fp->DirectIO (6,&n,&str); //Передал номер и название кассира
	if(err!=0) saveLog(IntToStr(err)+"(PrinterOPOS::OpenShift)"+GetStatusDescription(err));

	err=fp->SetVatValue(1,L"120");

	return err==0;
}
//---------------------------------------------------------------------------
bool PrinterOPOS::DayOpened()
{
	return fp->DayOpened;
}
//---------------------------------------------------------------------------
bool PrinterOPOS::setTrainingMode(bool val)
{
	if(getTrainingMode()==val) return true;
	long err;
	if(val) err=fp->BeginTraining(); else err=fp->EndTraining();

	AnsiString str=getLastError(&err);
	if(err!=0) saveLog(IntToStr(err)+"(PrinterOPOS::setTrainingMode)"+str);

	return err==0;
}
//---------------------------------------------------------------------------
bool PrinterOPOS::getTrainingMode()
{
	return fp->TrainingModeActive;
}
//---------------------------------------------------------------------------
//Заполнение массива с суммами по налоговым ставкам
bool PrinterOPOS::setNalog(long n,float stavka)
{
//	long s=stavka*100;
//	WideString str=IntToStr(s);
	BSTR s1=L"120";
	long err = fp->DirectIO(2, &n, &s1);
	if(err!=0) saveLog(IntToStr(err)+"(PrinterOPOS::OpenShift)"+GetStatusDescription(err));

	return err==0;
}
//---------------------------------------------------------------------------
//Дневной итог
int PrinterOPOS::getDaySum()
{
	try
	{
		long n=1;
		BSTR str=new OLECHAR[20];
		long err=fp->DirectIO (300,&n,&str);
		if(err!=0)throw Exception("["+IntToStr(err)+"]"+GetStatusDescription(err));
		return StrToInt(str)/100.0;
	}catch(...)
	{
		saveLog("(PrinterOPOS::getDaySum) Нет описания ошибки");
		dModule->SendError(1,"(PrinterOPOS::getDaySum) Нет описания ошибки");
		return 0;
	}
}
