Создаем Свои класс для взаимодействия с ООФ КазакТелеком
Объявим вспомогательные типы
type
TSellBuy = class(TObject)
Sum : Double;
PaymentType : Integer;
PaymentTypeName : String;
end;
type
TNonNullableType = record
Sell : Double;
Buy : Double;
ReturnSell : Double;
ReturnBuy : Double;
end;
Объявим тип для хранения данных ZX отчета
type
TZXReport = class(TObject)
ReportNumber : Integer;
TaxPayerName : String;
TaxPayerIN : String;
TaxPayerVAT : Boolean;
TaxPayerVATSeria : String;
TaxPayerVATNumber : String;
CashBoxSN : String;
CashBoxIN : String;
CashBoxRN : String;
StartOn : String;
ReportOn : String;
CloseOn : String;
CashierCode : Integer;
ShiftNumber : Integer;
DocumentCount : Integer;
PutMoneySum : Double;
TakeMoneySum : Double;
ControlSum : String;
OfflineMode : Boolean;
CashBoxOfflineMode : Boolean;
SumInCashbox : Double;
Sell : TList;{ of TSellBuy;}
Buy : TList {of TSellBuy;};
ReturnSell : TList {of TSellBuy;};
ReturnBuy : TList {of TSellBuy;};
StartNonNullable : TNonNullableType;
EndNonNullable : TNonNullableType;
end;
type
TControlTape = class(TObject)
OperationTypeText : String;
Sum : Double;
Date : String;
EmployeeCode : Integer;
Number : String;
isOffline : Boolean;
ExternalOperationId : String;
end;
Объявим тип для хранения данных кассы, в дальнейшем этот тип будем использовать чтобы авторизовываться в системе и для быстрой смены кассы
type
TCassa = class(Tobject)
UniqueNumber: String;
RegistrationNumber: String;
IdentificationNumber: String;
Name: String;
Description: String;
IsOffline: Boolean;
CurrentStatus: Integer;
Shift: Integer;
end;
Данный тип будем использовать как вспомогательный, чтобы хранить в
type
Tpayment = class(Tobject)
Sum: double;
PaymentType: LongInt;
end;
type
Tposition = class(Tobject)
Count: double;
Price: double;
Tax: double;
TaxType: LongInt;
PositionName: String;
PositionCode: double;
Discount: double;
Markup: double;
SectionCode: String;
IsStorno: Boolean;
UnitCode: Integer;
end;
type
TSendCheck = class(Tobject)
public
Token: String;
CashboxUniqueNumber: String;
OperationType: LongInt;
Position: TJSONArray;
Payment: TJSONArray;
Change: double;
RoundType: Integer;
ExternalCheckNumber: String;
CustomerEmail: String;
end;
type
Tcashbox = class(Tobject)
Unique_Number: String;
Registration_Number: String;
Identity_Number: String;
Address: String;
end;
type
TMoneyOperation = class(TObject)
OfflineMode : Boolean;
CashBoxOfflineMode : Boolean;
DateTime : String;
Sum : Double;
CashBox : Tcashbox;
end;
type
TAnswerCheck = class(Tobject)
public
var
check_number: String;
date_time: String;
offline_mode: Boolean;
cashbox: Tcashbox;
CheckOrderNumber: Integer;
ShiftNumber: Integer;
EmployeeName: String;
TicketUrl: String;
end;
Объявим наш основной тип, который будем использовать и добавлять в него наши функции
type
TOOFD = class(Tobject)
private const
API_URL = 'https://devkkm.webkassa.kz/api/';
var
HTTP: TIdHTTP;
SSLIO: TIdSSLIOHandlerSocketOpenSSL;
public
constructor Create();
destructor Destroy();
Добавим в наш основной тип функцию авторизации в ККМ системе, тут в принципе все довольно просто, используя REST API производится запрос по указанному адресу, в обратном ответе возвращается токен, по которому мы будем делать дальнейшие действия
function TOOFD.Autorize(login: String; passwd: String): String;
var
json_stream: TStringStream;
json_obj: TJSONObject;
json_pair: TJSONPair;
response: String;
Token: String;
json_response: TJSONObject;
begin
try
begin
Token := '';
json_obj := TJSONObject.Create;
json_pair := TJSONPair.Create('Login', login);
json_obj.AddPair(json_pair);
json_pair := TJSONPair.Create('Password', passwd);
json_obj.AddPair(json_pair);
json_stream := TStringStream.Create(json_obj.ToJSON);
response := HTTP.Post(API_URL + 'Authorize', json_stream);
json_response := TJSONObject.ParseJSONValue(response) as TJSONObject;
if (json_response.GetValue('Data') <> nil) then
begin
Token := (json_response.GetValue('Data') as TJSONObject)
.GetValue('Token').Value;
end
else
begin
// error code
ShowMessage(response);
Log(response);
end;
end;
finally
Result := Token;
json_obj.Free;
json_stream.Free;
end;
end;
Добавим функцию которая будет формировать чек и отправлять его в ООФД, тут нужно обратить внимание что чек может быть как "ЧЕК ПОКУПКИ" так и "ЧЕК ВОЗВРАТА" , основные данные которые мы должны отправить это
1.Наш токен - который мы получили при авторизации
2. Наш номер кассы который мы получили когда зарегистрировались в ООФД
3. Вид операции (покупка или возврат)
4. Позиции в чеке - товары или услуги которые мы продаем или по которым делаем возврат
5. Оплаты или Возвраты сумма и количество
Это основные данные которые мы должны передать на сервер ККМ ООФД,
в ответ мы получаем тип который мы описали ранее type TAnswerCheck
function TOOFD.Check(SendCheck: TSendCheck): TAnswerCheck;
var
json_stream, jsst: TStringStream;
json_obj: TJSONObject;
json_pair: TJSONPair;
response: String;
json_response: TJSONObject;
json_data: TJSONObject;
answer: TAnswerCheck;
cashbox: Tcashbox;
cashbox_json: TJSONObject;
js_str: String;
begin
try
json_obj := TJSONObject.Create;
json_obj.AddPair('Token', SendCheck.Token);
json_obj.AddPair('CashBoxUniqueNumber', SendCheck.CashboxUniqueNumber);
json_obj.AddPair('OperationType',
TJSONNumber.Create(SendCheck.OperationType));
json_obj.AddPair('Positions', SendCheck.Position);
json_obj.AddPair('Payments', SendCheck.Payment);
json_stream := TStringStream.Create(json_obj.ToJSON);
answer := TAnswerCheck.Create;
response := HTTP.Post(API_URL + 'Check', json_stream);
Log(response);
json_response := (TJSONObject.ParseJSONValue(response) as TJSONObject);
if (json_response.GetValue('Data') <> nil) then
begin
json_data := (json_response.GetValue('Data') as TJSONObject);
answer.check_number := json_data.GetValue('CheckNumber').Value;
answer.date_time := json_data.GetValue('DateTime').Value;
answer.offline_mode := StrToBool(json_data.GetValue('OfflineMode').Value);
cashbox_json := (json_data.GetValue('Cashbox') as TJSONObject);
answer.cashbox := Tcashbox.Create;
answer.cashbox.Unique_Number :=
cashbox_json.GetValue('UniqueNumber').Value;
answer.cashbox.Registration_Number :=
cashbox_json.GetValue('RegistrationNumber').Value;
answer.cashbox.Identity_Number :=
cashbox_json.GetValue('IdentityNumber').Value;
answer.cashbox.Address := cashbox_json.GetValue('Address').Value;
answer.CheckOrderNumber :=
StrToInt(json_data.GetValue('CheckOrderNumber').Value);
answer.ShiftNumber := StrToInt(json_data.GetValue('ShiftNumber').Value);
answer.EmployeeName := json_data.GetValue('EmployeeName').Value;
answer.TicketUrl := json_data.GetValue('TicketUrl').Value;
end
else
begin
// error code
ShowMessage(response);
end;
finally
Result := answer;
json_stream.Free;
json_response.Free;
end;
end;
Добавляем функцию которая позволит нам получить информацию по ZX Отчетам
function TOOFD.ZReport(Token: String; CassaNumber: String): TZXReport;
var
json_obj,
json_data,
json_response,
json_ennon: TJSONObject;
json_stream: TStringStream;
response: String;
res : TZXReport;
sell,
buy :TSellBuy;
i : Integer;
sell_js_arr : TJSONArray;
type_pay : Integer;
begin
try
DialogProgress;
json_obj := TJSONObject.Create;
res := TZXReport.Create;
json_obj.AddPair('Token', Token);
json_obj.AddPair('CashBoxUniqueNumber', CassaNumber);
json_stream := TStringStream.Create(json_obj.ToJSON);
response := HTTP.Post(API_URL + 'ZReport', json_stream);
Log(response);
json_response := (TJSONObject.ParseJSONValue(response) as TJSONObject);
if (json_response.GetValue('Data') <> nil) then
begin
json_data := (json_response.GetValue('Data') as TJSONObject);
res.ReportNumber := StrToInt(json_data.GetValue('ReportNumber').Value);
res.TaxPayerName := json_data.GetValue('TaxPayerName').Value;
res.TaxPayerIN := json_data.GetValue('TaxPayerIN').Value;
res.TaxPayerVAT := StrToBool(json_data.GetValue('TaxPayerVAT').Value);
res.TaxPayerVATSeria := json_data.GetValue('TaxPayerVATSeria').Value;
res.TaxPayerVATNumber := json_data.GetValue('TaxPayerVATNumber').Value;
res.CashBoxSN := json_data.GetValue('CashboxSN').Value;
res.CashBoxIN := json_data.GetValue('CashboxIN').Value;
res.CashBoxRN := json_data.GetValue('CashboxRN').Value;
res.StartOn := json_data.GetValue('StartOn').Value;
res.ReportOn := json_data.GetValue('ReportOn').Value;
res.CloseOn := json_data.GetValue('CloseOn').Value;
res.CashierCode := StrToInt(json_data.GetValue('CashierCode').Value);
res.ShiftNumber := StrToInt(json_data.GetValue('ShiftNumber').Value);
res.DocumentCount := StrToInt(json_data.GetValue('DocumentCount').Value);
res.PutMoneySum := StrToFloat(json_data.GetValue('PutMoneySum').Value);
res.TakeMoneySum := StrToFloat(json_data.GetValue('TakeMoneySum').Value);
res.ControlSum := (json_data.GetValue('ControlSum').Value);
res.OfflineMode := StrToBool(json_data.GetValue('OfflineMode').Value);
res.CashBoxOfflineMode := StrToBool(json_data.GetValue('CashboxOfflineMode').Value);
res.SumInCashbox := StrToFloat(json_data.GetValue('SumInCashbox').Value);
sell_js_arr :=(TJSONObject(json_data.GetValue('Sell')).GetValue('PaymentsByTypesApiModel') as TJSONArray);
res.Sell := TList.Create;
for i := 0 to sell_js_arr.Count-1 do
begin
sell := TSellBuy.Create;
sell.Sum := StrToFloat(TJSONObject(sell_js_arr.Items[i]).GetValue('Sum').Value);
type_pay := StrToInt(TJSONObject(sell_js_arr.Items[i]).GetValue('Type').Value);
case type_pay of
0 : sell.PaymentTypeName := 'Наличные';
1 : sell.PaymentTypeName := 'Банковская карта';
2 : sell.PaymentTypeName := 'Оплата в кредит';
3 : sell.PaymentTypeName := 'Оплата тарой';
end;
res.Sell.Add(sell);
end;
res.EndNonNullable.Sell := StrToFloat(TJSONObject(json_data.GetValue('EndNonNullable')).GetValue('Sell').Value);
res.EndNonNullable.Buy := StrToFloat(TJSONObject(json_data.GetValue('EndNonNullable')).GetValue('Buy').Value);
res.EndNonNullable.ReturnSell := StrToFloat(TJSONObject(json_data.GetValue('EndNonNullable')).GetValue('ReturnSell').Value);
res.EndNonNullable.ReturnBuy := StrToFloat(TJSONObject(json_data.GetValue('EndNonNullable')).GetValue('ReturnBuy').Value);
res.StartNonNullable.Sell := StrToFloat(TJSONObject(json_data.GetValue('StartNonNullable')).GetValue('Sell').Value);
res.StartNonNullable.Buy := StrToFloat(TJSONObject(json_data.GetValue('StartNonNullable')).GetValue('Buy').Value);
res.StartNonNullable.ReturnSell := StrToFloat(TJSONObject(json_data.GetValue('StartNonNullable')).GetValue('ReturnSell').Value);
res.StartNonNullable.ReturnBuy := StrToFloat(TJSONObject(json_data.GetValue('StartNonNullable')).GetValue('ReturnBuy').Value);
// res.ReturnSell.Add();
end
else
begin
ShowMessage(response);
end;
finally
Result := res;
CloseDialog;
end;
end;
Пример использования OOFD
if(not Assigned(OOFD))then
OOFD := TOOFD.Create;
sum := StrToFloat(edt_sum.Text);
moneyOperation := OOFD.MoneyOperation(MainForm.WEBCASSA_HASH_KEY,UniqueNumberSelect,0,sum,'');
LoadReport(326);
frxReport1.Variables['SNCASSA']:=QuotedStr(moneyOperation.CashBox.Unique_Number);
frxReport1.Variables['REGNUMCASSA']:=QuotedStr(moneyOperation.CashBox.Registration_Number);
frxReport1.Variables['SUMOPER']:=QuotedStr(FloatToStr(sum));
frxReport1.Variables['SUMMA']:=QuotedStr(FloatToStr(moneyOperation.Sum));
frxReport1.Variables['ZAVNUMCASSA']:=QuotedStr(moneyOperation.CashBox.Identity_Number);
ShowReport;
Комментарии (17)
idelgujin
01.09.2022 07:32+2Когда эти грёбаные кассы научат общаться по json? Что проще - отправить строки с позициями, ценами и количеством?
HemulGM
01.09.2022 07:50+1Давно научились. uses REST.Json;
var ZXReport := TJSON.JsonToObject<TZXReport>(JSON); одна строка заменяет пелену перечисления и парсинга полей
Slym99
01.09.2022 07:34+4ааа!!!
0. const в параметрах придумали не зря...
1. длинный неразделяемый код.
2. JSON умеет по другому!
2.1. JSON умеет сразу с типизацией GetValue<double>('') без StrToFloat, а сколько копий сломано о дробном разделителе я молчу...
2.2. JSON умеет JPath: не надо StrToFloat(TJSONObject(json_data.GetValue('EndNonNullable')).GetValue('Sell').Value);
делай так: GetValue<double>('EndNonNullable.Sell')
3. as ЗЛО!!!! как и free неинициализированных переменных
для парсинга JSON обычно достаточно типов TJSONValue (GetValue<double>, GetValue<TJSONArray>) и TJSONArray
4. Delphi славится своей RTTI - ни капли рефлексии не увидел, хотя подобный парсинг легко ложится в RTTI маршализацию
5. проверка наличия объекта с поиском по имени, и потом его повторный поиск для использования - да кто эти такты считает!
if (json_response.GetValue('Data') <> nil) then begin json_data := (json_response.GetValue('Data') as TJSONObject);HemulGM
01.09.2022 07:59+2Добавлю ещё, что я как минимум в одном месте вижу, что не освобождается объект json.
Функция Check, переменная json_obj. И в целом код не очень хороший. Вы многие проблемы описали, но очевидно их ещё больше, но вникать не очень хочется.
- Ошибки в ShowMessage - тоже не лучшая идея.
- Список Sell в классе TZXReport создается извне, а не в конструкторе, при чем ещё и не типизированный (вероятно ТС вообще не умеет работать с дженериками).
- Парсинг json_response := (TJSONObject.ParseJSONValue(response) as TJSONObject);
Вообще может привести к прямой ошибке, т.к. парсинг пустого ответа вернёт nil, а "as" добьёт.
Здесь должно быть
json_response := TJSONObject.ParseJSONValue(response);
if Assigned(json_response) then
try
// а приводить json_response к TJSONObject не имеет смысла
finally
json_response.Free;
end;
Проверять полученное значение через GetValue тоже не обязательно, есть TryGetValue, что более удобно и наглядно.
Код можно сократить и упростить в 2, 3 или даже 4 раза.
Ещё, я бы использовал не Indy клиент, а System.Net.HttpClient или вообще сразу RESTClientAn_private
01.09.2022 12:27Господа, несколько оффтоп, но не посоветуете - где можно обзорно хотя бы почитать про все возможности Delphi?
Поясню. Я не программист по специальности, но по работе мне постоянно приходится решать задачи разного уровня сложности через программирование (от работы с видео до работы с REST API видеосерверов). На паскале сижу уже больше 30 лет (от турбо паскаля через все версии Delphi). И очень часто просматривая свой же код двух-трёх летней давности я с огорчением обнаруживаю, что там я изобрёл очередной велосипед, который штатно решается средствами самого Delphi.
То есть мне не нужно детальное описание всего - мне нужно иметь понимание в общих чертах - если мне понадобятся детали реализации нужного - дальше я уже нагуглю.
AndyKorg
02.09.2022 07:41Как всегда - у владельца языка самая полная информация. Другой вопрос, что она там не очень удобно подается и не вся. Приходится лазить по всяким блогам и влогам :)
An_private
02.09.2022 10:21Не очень удобно - это очень мягко сказано. Нет общей системы. Нет обзора фич.
Ну вот, пример. Я много лет постоянно использую TList и TThreadList. И всё время удивлялся - почему нельзя сразу указать тип объектов, хранящихся в листе, чтобы потом при каждом обращении не указывать тип объекта. И лишь где-то год назад совершенно случайно нашёл, что оказывается есть collections, в котором можно писать TList<TObjectType>. Причём из хелпа на TList нет никаких указаний на такую возможность - то есть должен просто знать. И я подозреваю, что подобных велосипедов я изобретаю ещё множество - просто по незнанию. Изучать полный справочник по языку - ну то ещё занятие. Вот было бы где-то что-то обзорное, без деталей....
HemulGM
02.09.2022 08:46Я уже 8 лет работаю с Делфи и до сих пор встречаю как в языке, так и в стандартной библиотеке или среде что-то, что ещё не использовал.
Многое нахожу сам, когда пытаюсь реализовать что-то и копаюсь в библиотеке. Например, многие приемы и вещи можно подсмотреть у самих разработчиков, ведь их код библиотек доступен сразу в среде. Как они сделали вот это, а как то и т.д.
Если нужно, пишу в чаты в ТГ, там и происходит обмен опытом.
Справкой или же онлайн wiki пользуюсь уже тогда, когда сам предмет известен и нужно почитать подробнее как он работает.
Например, совсем недавно узнал о том, что в Делфи есть кроссплатформенная система сообщений System.Messaging, которая позволяет подписываться на сообщения, в которых могут приходить объекты и прочая информация. К сожалению, она не потокобезопасна пока.An_private
02.09.2022 10:25Вот вот. И это очень обидно - в очередной раз обнаруживать, что ты потратил кучу времени на написание очередного велосипеда, который, оказывается, штатно реализуется буквально парой строк кода.
itHauntsMe
02.09.2022 13:51+2Рекомендую Marco Cantu - Object Pascal Handbook. Современная (2020 г.), обзорная (начинает с самых основ) книга с примерами (есть гитхаб).
Если хочется чего-то более глубокого, то есть сборник разнородных рецептов Daniele Teti - Delphi Cookbook, есть гайд по паттернам и параллельности Nick Hodges - More Coding In Delphi. Есть еще целая серия книг от Dalija Prasnikar.
По многопоточному программированию есть хорошая бесплатная книга на русском - https://github.com/loginov-dmitry/multithread.
Также рекомендую полностью прочитать блог GunSmoker'а - очень полезно.
GreekIgor Автор
01.09.2022 08:26+3Большое спасибо за грамотные комментарии, было очень интересно почитать, людей разбирающихся в теме, про себя могу добавить я самоучка - многие моменты может слишком тяжело написаны, но опыта разбирающихся людей как раз и не хватает , большое спасибо за уделенное время моему посту
mmMike
А может не надо превращать хабр в непонятно что.
Вы так уверены, что обрывки кода, написанные на экзотическом на данный момент языке и для весьма узкой и специфичной задачи кому то интересны?
HemulGM
А вы уверены, что заходить в пост, под не интересующим вас тегом разумная идея?
SmallDonkey
Экзотический язык? Вы серьёзно? Давно на сайте embarcadero были?