FdcServerHostApp.cs 163 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794179517961797179817991800180118021803180418051806180718081809181018111812181318141815181618171818181918201821182218231824182518261827182818291830183118321833183418351836183718381839184018411842184318441845184618471848184918501851185218531854185518561857185818591860186118621863186418651866186718681869187018711872187318741875187618771878187918801881188218831884188518861887188818891890189118921893189418951896189718981899190019011902190319041905190619071908190919101911191219131914191519161917191819191920192119221923192419251926192719281929193019311932193319341935193619371938193919401941194219431944194519461947194819491950195119521953195419551956195719581959196019611962196319641965196619671968196919701971197219731974197519761977197819791980198119821983198419851986198719881989199019911992199319941995199619971998199920002001200220032004200520062007200820092010201120122013201420152016201720182019202020212022202320242025202620272028202920302031203220332034203520362037203820392040204120422043204420452046204720482049205020512052205320542055205620572058205920602061206220632064206520662067206820692070207120722073207420752076207720782079208020812082208320842085208620872088208920902091209220932094209520962097209820992100210121022103210421052106210721082109211021112112211321142115211621172118211921202121212221232124212521262127212821292130213121322133213421352136213721382139214021412142214321442145214621472148214921502151215221532154215521562157215821592160216121622163216421652166216721682169217021712172217321742175217621772178217921802181218221832184218521862187218821892190219121922193219421952196219721982199220022012202220322042205220622072208220922102211221222132214221522162217221822192220222122222223222422252226222722282229223022312232223322342235223622372238223922402241224222432244224522462247224822492250225122522253225422552256225722582259226022612262226322642265226622672268226922702271227222732274227522762277227822792280228122822283228422852286228722882289229022912292229322942295229622972298229923002301230223032304230523062307230823092310231123122313231423152316231723182319232023212322232323242325232623272328232923302331233223332334233523362337233823392340234123422343234423452346234723482349235023512352235323542355235623572358235923602361236223632364236523662367236823692370237123722373237423752376237723782379238023812382238323842385238623872388238923902391239223932394239523962397239823992400240124022403240424052406240724082409241024112412241324142415241624172418241924202421242224232424242524262427242824292430243124322433243424352436243724382439244024412442244324442445244624472448244924502451245224532454245524562457245824592460246124622463246424652466246724682469247024712472247324742475247624772478247924802481248224832484248524862487248824892490249124922493249424952496249724982499250025012502250325042505250625072508250925102511251225132514251525162517251825192520252125222523252425252526252725282529253025312532253325342535253625372538253925402541254225432544254525462547254825492550255125522553255425552556255725582559256025612562256325642565256625672568256925702571257225732574257525762577257825792580258125822583258425852586258725882589259025912592259325942595259625972598259926002601260226032604260526062607260826092610261126122613261426152616261726182619262026212622262326242625262626272628262926302631263226332634263526362637263826392640264126422643264426452646264726482649265026512652265326542655265626572658265926602661266226632664266526662667
  1. using Edge.Core.Configuration;
  2. using Edge.Core.Database;
  3. using Edge.Core.Database.Models;
  4. using Edge.Core.IndustryStandardInterface.ATG;
  5. using Edge.Core.IndustryStandardInterface.Pump;
  6. using Edge.Core.Processor;
  7. using Edge.Core.Processor.Dispatcher.Attributes;
  8. using Edge.Core.UniversalApi;
  9. using FdcServerHost;
  10. using Microsoft.EntityFrameworkCore;
  11. using Microsoft.Extensions.DependencyInjection;
  12. using Microsoft.Extensions.Logging;
  13. using System;
  14. using System.Collections.Generic;
  15. using System.Linq;
  16. using System.Text.Json;
  17. using System.Text.RegularExpressions;
  18. using System.Threading.Tasks;
  19. using Wayne.FDCPOSInterface;
  20. using Wayne.FDCPOSLibrary;
  21. namespace Applications.FDC
  22. {
  23. [UniversalApi(Name = "OnFdcControllerStateChange", EventDataType = typeof(FdcPumpControllerOnStateChangeEventArg), Description = "When pump state changed, the event will fired")]
  24. [UniversalApi(Name = "OnError", EventDataType = typeof(string))]
  25. [UniversalApi(Name = "OnCurrentFuellingStatusChange", EventDataType = typeof(FdcServerTransactionDoneEventArg), Description = "When pump in fueling state, the fueling progress will be reported via this event")]
  26. [UniversalApi(Name = "OnFdcFuelSaleTransactinStateChange", EventDataType = typeof(FdcFuelSaleTransactinStateChangeEventArg), Description = "When pump transaction state changed, the event will fired")]
  27. [MetaPartsDescriptor(
  28. "lang-zh-cn:Fdc 服务器 Applang-en-us:Fdc Server App",
  29. "lang-zh-cn:用于管理已经配置和连接在此FCC中的所有加油机,并提供基于 IFSF-POS-FDC 协议定义的各类接口" +
  30. "lang-en-us:Used for manage all pumps configed and connected in this FCC, and providing varies of APIs based on IFSF-FDC-POS protocol",
  31. new[] { "lang-zh-cn:加油机lang-en-us:Pump", "lang-zh-cn:IfsfFdcServerlang-en-us:IfsfFdcServer" })]
  32. public class FdcServerHostApp : IAppProcessor
  33. {
  34. [UniversalApi(Description = "Get the overall pumps, nozzles info.")]
  35. public async Task<IEnumerable<object>> GetPumpsLayout(ApiData input)
  36. {
  37. //if (input == null || input.Parameters == null || !input.Parameters.Any())
  38. // return this.fdcPumpControllers;
  39. //var targetPumpIds = input.Parameters.Where(v => v.Name != null && v.Name.ToLower() == "pumpid" && int.TryParse(v.Value, out _))
  40. // .Select(v => int.Parse(v.Value));
  41. ////if (!targetPumpIds.Any()) return this.fdcPumpControllers;
  42. return this.fdcPumpControllers.Select(p => new
  43. {
  44. p.Name,
  45. p.PumpId,
  46. Nozzles = p.Nozzles.Select(n => new
  47. {
  48. n.LogicalId,
  49. n.RealPriceOnPhysicalPump,
  50. SiteLevelNozzleId = this.nozzleExtraInfos.FirstOrDefault(c =>
  51. c.PumpId == n.PumpId && c.NozzleLogicalId == n.LogicalId)?.SiteLevelNozzleId,
  52. ProductBarcode = this.nozzleExtraInfos.FirstOrDefault(c =>
  53. c.PumpId == n.PumpId && c.NozzleLogicalId == n.LogicalId)?.ProductBarcode,
  54. ProductName = this.nozzleExtraInfos.FirstOrDefault(c =>
  55. c.PumpId == n.PumpId && c.NozzleLogicalId == n.LogicalId)?.ProductName,
  56. }),
  57. p.AmountDecimalDigits,
  58. p.VolumeDecimalDigits,
  59. p.PriceDecimalDigits,
  60. p.VolumeTotalizerDecimalDigits
  61. });
  62. }
  63. [UniversalApi(Description = "Get FuelSaleTrx Details info by searching conditions. </br>" +
  64. "Searching condition 1: by providing para.Name=='ReleaseToken' will ignore other conditions.</br>" +
  65. "Searching condition 2: para.Name=='PumpId' and para.Name=='LogicalNozzleId' must be provided, and para.Name=='TransactionNumber' is optional.")]
  66. public async Task<IEnumerable<object>> GetFuelSaleTrxDetailsAsync(ApiData input)
  67. {
  68. if (input == null || input.Parameters == null || !input.Parameters.Any()) throw new ArgumentException(nameof(input));
  69. List<FuelSaleTransaction> transactions;
  70. if (int.TryParse(input.Parameters.FirstOrDefault(p => p.Name.ToLower() == "releasetoken")?.Value, out int targetReleaseToken))
  71. {
  72. SqliteDbContext dbContext = new SqliteDbContext();
  73. transactions = await dbContext.PumpTransactionModels.Where(t => t.ReleaseToken == targetReleaseToken).ToListAsync();
  74. }
  75. else
  76. {
  77. if (!int.TryParse(input.Parameters.FirstOrDefault(p => p.Name.ToLower() == "pumpid")?.Value, out int targetPumpId)
  78. || !int.TryParse(input.Parameters.FirstOrDefault(p => p.Name.ToLower() == "logicalnozzleid")?.Value, out int targetLogicalNozzleId))
  79. throw new ArgumentException("Must provide valid parameter value for PumpId and LogicalNozzleId");
  80. var targetTransactionNumber = input.Parameters.FirstOrDefault(p => p.Name.ToLower() == "transactionnumber")?.Value;
  81. SqliteDbContext dbContext = new SqliteDbContext();
  82. int maxReturnDataCount = 50;
  83. if (string.IsNullOrEmpty(targetTransactionNumber))
  84. transactions = await dbContext.PumpTransactionModels.Where(t =>
  85. t.PumpId == targetPumpId && t.LogicalNozzleId == targetLogicalNozzleId).OrderByDescending(t => t.SaleStartTime).Take(maxReturnDataCount).ToListAsync();
  86. else
  87. transactions = await dbContext.PumpTransactionModels.Where(t =>
  88. t.PumpId == targetPumpId && t.LogicalNozzleId == targetLogicalNozzleId
  89. && t.TransactionSeqNumberFromPhysicalPump == targetTransactionNumber).OrderByDescending(t => t.SaleStartTime).Take(maxReturnDataCount).ToListAsync();
  90. }
  91. return transactions.Select(trx =>
  92. {
  93. //NOTE, the queried trx may from old times, but the this.fdcPumpControllers and this.nozzleProductConfig
  94. //are always the latest, so they may mismatch and produce incorrect data.
  95. var pumpHandler = this.fdcPumpControllers.FirstOrDefault(fpc => fpc.PumpId == trx.PumpId);
  96. return new
  97. {
  98. trx.ReleaseToken,
  99. trx.PumpId,
  100. trx.LogicalNozzleId,
  101. SiteLevelNozzleId = this.nozzleExtraInfos.FirstOrDefault(c =>
  102. c.PumpId == trx.PumpId && c.NozzleLogicalId == trx.LogicalNozzleId)?.SiteLevelNozzleId,
  103. trx.TransactionSeqNumberFromPhysicalPump,
  104. trx.ProductBarcode,
  105. ProductName = this.nozzleExtraInfos.FirstOrDefault(c =>
  106. c.PumpId == trx.PumpId && c.NozzleLogicalId == trx.LogicalNozzleId)?.ProductName,
  107. RawAmount = trx.Amount,
  108. Amount = pumpHandler == null ? (double?)null : (trx.Amount / Math.Pow(10, pumpHandler.AmountDecimalDigits)),
  109. RawVolume = trx.Volumn,
  110. Volume = pumpHandler == null ? (double?)null : trx.Volumn / Math.Pow(10, pumpHandler.VolumeDecimalDigits),
  111. RawPrice = trx.UnitPrice,
  112. Price = pumpHandler == null ? (double?)null : trx.UnitPrice / Math.Pow(10, pumpHandler.PriceDecimalDigits),
  113. RawAmountTotalizer = trx.AmountTotalizer,
  114. AmountTotalizer = pumpHandler == null ? (double?)null : trx.AmountTotalizer / Math.Pow(10, pumpHandler.AmountDecimalDigits),
  115. RawVolumeTotalizer = trx.VolumeTotalizer,
  116. VolumeTotalizer = pumpHandler == null ? (double?)null : trx.VolumeTotalizer / Math.Pow(10, pumpHandler.VolumeDecimalDigits),
  117. trx.State,
  118. trx.SaleStartTime,
  119. trx.SaleEndTime,
  120. };
  121. });
  122. }
  123. [UniversalApi]
  124. public int PumpCount => this.fdcPumpControllers?.Count ?? 0;
  125. [UniversalApi]
  126. public async Task<IEnumerable<object>> GetDeviceState(ApiData input)
  127. {
  128. List<object> resultList = new List<object>();
  129. foreach (var controller in this.FdcPumpControllers)
  130. {
  131. var state = await controller.QueryStatusAsync();
  132. //var nozzles = controller.Nozzles.Select(n => new
  133. //{
  134. // n.LogicalId,
  135. // VolumeTotalizer = GetVolumeTotalizer(controller.PumpId, n.LogicalId),
  136. // Amount = GetLatestTransactionAmount(controller.PumpId, n.LogicalId),
  137. // n.LogicalState
  138. //});
  139. List<object> nozzles = new List<object>();
  140. foreach (var n in controller.Nozzles)
  141. {
  142. if (n.VolumeTotalizer == null)
  143. {
  144. var result = await GetFuelPointTotalsAsync(controller.PumpId, n.LogicalId);
  145. n.VolumeTotalizer = (int?)(result.Item2 * Math.Pow(10, controller.VolumeDecimalDigits));
  146. }
  147. var Amount = GetLatestTransactionAmount(controller.PumpId, n.LogicalId);
  148. nozzles.Add(new { n.LogicalId, n.VolumeTotalizer, Amount });
  149. }
  150. resultList.Add(new { controller.PumpId, DeviceState = state, Nozzles = nozzles });
  151. }
  152. return resultList;
  153. }
  154. private int GetLatestTransactionAmount(int pumpId, int nozzleId)
  155. {
  156. var dueDate = DateTime.Now.Subtract(new TimeSpan(30, 0, 0, 0));
  157. SqliteDbContext dbContext = new SqliteDbContext();
  158. var trans = dbContext.PumpTransactionModels.Where(t => t.State != FuelSaleTransactionState.Cleared
  159. && t.PumpId == pumpId && t.LogicalNozzleId == nozzleId
  160. && t.SaleStartTime >= dueDate).OrderByDescending(r => r.SaleStartTime).Take(1);
  161. return trans.Any() ? trans.FirstOrDefault().Amount : 0;
  162. }
  163. [UniversalApi(Description = "Get lastest 1000 count of Payable or Locked state fuel trx for nozzles." +
  164. "</br>input parameters are explained as below, " +
  165. "</br>para.Name==\"nozzleid\" nozzle logical id" +
  166. "</br>\"pumpid\" the target pump id" +
  167. "</br>Leave input as Null to get for all nozzles in all pumps")]
  168. public async Task<object> GetLastAvailableTransaction(ApiData input)
  169. {
  170. var nozzleId = -1;
  171. var pumpId = -1;
  172. if (input != null && !input.IsEmpty())//with no search condition specified, return latest 1000 records.
  173. {
  174. var temp = "";
  175. foreach (var p in input.Parameters)
  176. {
  177. temp += p.Name + " " + p.Value + " ";
  178. }
  179. //fdcLogger.LogDebug("input is not null, parameters:{0}", temp);
  180. nozzleId = input.Get("nozzleid", -1);
  181. pumpId = input.Get("pumpid", -1);
  182. }
  183. fdcLogger.LogDebug("GetAvailableTransactions for nozzle:{0},pump:{1}", nozzleId, pumpId);
  184. List<object> resultList = new List<object>();
  185. var result = await GetAvailableFuelSaleTrxsWithDetailsAsync(pumpId, nozzleId, 1000);
  186. if (result != null && result.Any())
  187. {
  188. var all = result.GroupBy(r => new { r.PumpId, r.LogicalNozzleId });
  189. foreach (var a in all)
  190. {
  191. var trans = a.Where(_ =>
  192. _.State == FuelSaleTransactionState.Payable || _.State == FuelSaleTransactionState.Locked);
  193. resultList.Add(new { a.Key.PumpId, a.Key.LogicalNozzleId, hasPayableTransactions = trans.Any(), trans.FirstOrDefault().VolumeTotalizer, trans.FirstOrDefault().Amount });
  194. }
  195. return resultList;
  196. }
  197. resultList.Add(new { LogicalNozzleId = nozzleId, PumpId = pumpId, hasPayableTransactions = false });
  198. return resultList;
  199. }
  200. private bool config_AutoAuthCallingPumps;
  201. private int config_MaxStackUnpaidTrxPerPump;
  202. private int config_ListeningPort;
  203. private FDCPOSInterfaceServer fdcServer;
  204. public string MetaConfigName { get; set; }
  205. private IEnumerable<NozzleExtraInfo> nozzleExtraInfos;
  206. private List<IFdcPumpController> fdcPumpControllers;
  207. private List<IFdcCommunicableController> fdcCommunicableControllers;
  208. private List<IAutoTankGaugeController> autoTankGaugeControllers = new List<IAutoTankGaugeController>();
  209. private IServiceProvider services;
  210. private Configurator configurator;
  211. private EventHandler onConfiguratorConfigFileChangedEventHandler;
  212. private System.Timers.Timer purgeDatabaseTimer;
  213. static ILogger fdcLogger;//= NLog.LogManager.LoadConfiguration("nlog.config").GetLogger("FdcServer");
  214. static ILogger cloudLogger;// = NLog.LogManager.LoadConfiguration("nlog.config").GetLogger("CloudRestClient");
  215. private object syncObject = new object();
  216. enum FDCLogicalState
  217. {
  218. FDC_LOGICAL_ST_UNLOCKED = 0,
  219. FDC_LOGICAL_ST_LOCKED = 1,
  220. };
  221. #region App configs
  222. /// <summary>
  223. /// when doing major schema change of the config class, the class should not be modify directly.
  224. /// instead, for better fallback support, create a new class with a version suffix, and also
  225. /// update the version string in appCtorParamsJsonSchema.json file.
  226. /// </summary>
  227. public class AppConfigV1
  228. {
  229. public bool AutoAuthCallingPumps { get; set; }
  230. public int MaxStackUnpaidTrxPerPump { get; set; }
  231. public int FdcServerListeningPort { get; set; }
  232. public int? PurgePayableTrxOlderThanByMin { get; set; }
  233. public int? PurgeClearedTrxOlderThanByDay { get; set; }
  234. public List<AppProductConfigV1> ProductConfigs { get; set; }
  235. public List<AppPumpConfigV1> PumpConfigs { get; set; }
  236. }
  237. public class AppPumpConfigV1
  238. {
  239. public int PumpId { get; set; }
  240. public List<AppNozzleConfigV1> NozzleConfigs { get; set; }
  241. }
  242. public class AppProductConfigV1
  243. {
  244. public int ProductCode { get; set; }
  245. public string ProductName { get; set; }
  246. }
  247. public class AppNozzleConfigV1
  248. {
  249. public int NozzleLogicalId { get; set; }
  250. public int? SiteLevelNozzleId { get; set; }
  251. public int ProductCode { get; set; }
  252. /// <summary>
  253. /// Gets or sets the tank number of this nozzle undergroud linked.
  254. /// </summary>
  255. public int? TankNumber { get; set; }
  256. public string Description { get; set; }
  257. }
  258. #endregion
  259. /// <summary>
  260. /// A callback function that will be called on DefaultDispatcher(a framework util for instantiate App and DeviceHandler)
  261. /// is trying to serialize param strings(most likely saved into a database) into params of this class's ctor, and got an error.
  262. ///
  263. /// should handle the json str that cause the serialize error, resolve and reformat it to a compatible content.
  264. /// </summary>
  265. /// <param name="incompatibleCtorParamsJsonStr">sample: [{"UITemplateVersion":"1.1","AutoAuthCallingPumps":false,"MaxStackUnpaidTrxPerPump":3}, "2nd string parameter", 3]</param>
  266. /// <returns>need follow format of json array, sample: [{firstObjectParameter},"2nd string parameter",3]</returns>
  267. private static List<object> ResolveCtorMetaPartsConfigCompatibility(string incompatibleCtorParamsJsonStr)
  268. {
  269. var jsonParams = JsonDocument.Parse(incompatibleCtorParamsJsonStr).RootElement.EnumerateArray().ToArray();
  270. //sample: "UITemplateVersion":"1.0"
  271. string uiTemplateVersionRegex = @"(?<=""UITemplateVersion""\:\"").+?(?="")";
  272. var match = Regex.Match(jsonParams.First().GetRawText(), uiTemplateVersionRegex, RegexOptions.IgnoreCase | RegexOptions.Multiline);
  273. if (match.Success)
  274. {
  275. // the curVersion string has been found, can handle it with proper fallback.
  276. var curVersion = match.Value;
  277. if (curVersion == "1.0")
  278. {
  279. var existsAppConfigV1 = JsonSerializer.Deserialize(jsonParams.First().GetRawText(), typeof(AppConfigV1));
  280. // AppConfigV2 appConfigV2 = new AppConfigV2();
  281. // appConfigV2.aaa = existsAppConfigV1.aaaa;
  282. // appConfigV2.bbb = existsAppConfigV1.bbbb;
  283. // ...
  284. // return new List<object>(){appConfigV2};
  285. }
  286. //else if(curVersion == "2.0")
  287. //{
  288. // var existsAppConfigV2 = JsonSerializer.Deserialize(jsonParams.First().GetRawText(), typeof(AppConfigV2));
  289. // AppConfigV3 appConfigV3 = new AppConfigV3();
  290. // appConfigV3.aaa = existsAppConfigV2.aaaa;
  291. // appConfigV3.bbb = existsAppConfigV2.bbbb;
  292. // ...
  293. // return new List<object>(){appConfigV3};
  294. //}
  295. }
  296. else
  297. {
  298. // try fallback as best as you can by reading the raw input string,
  299. // if you have no idea to do any fallback, then return a most common config for let the user can go through easier.
  300. return new List<object>() {
  301. new AppConfigV1() {
  302. AutoAuthCallingPumps = false,
  303. FdcServerListeningPort = 4711,
  304. MaxStackUnpaidTrxPerPump = 3 } };
  305. }
  306. return new List<object>() {
  307. new AppConfigV1() {
  308. AutoAuthCallingPumps = false,
  309. FdcServerListeningPort = 4711,
  310. MaxStackUnpaidTrxPerPump = 3 } };
  311. }
  312. public Task Test(params object[] parameters)
  313. {
  314. if ((!this.fdcServer?.IsStarted) ?? false)
  315. throw new InvalidOperationException("Fdc server failed to start on a port");
  316. return Task.CompletedTask;
  317. }
  318. [ParamsJsonSchemas("appCtorParamsJsonSchema")]
  319. public FdcServerHostApp(AppConfigV1 appConfig, IServiceProvider services)
  320. {
  321. this.config_AutoAuthCallingPumps = appConfig.AutoAuthCallingPumps;
  322. this.config_MaxStackUnpaidTrxPerPump = appConfig.MaxStackUnpaidTrxPerPump;
  323. this.config_ListeningPort = appConfig.FdcServerListeningPort;
  324. if (appConfig.PurgeClearedTrxOlderThanByDay.HasValue || appConfig.PurgePayableTrxOlderThanByMin.HasValue)
  325. {
  326. this.purgeDatabaseTimer = new System.Timers.Timer();
  327. this.purgeDatabaseTimer.Interval = 1000 * 60;
  328. this.purgeDatabaseTimer.Elapsed += (s, a) =>
  329. {
  330. if (appConfig.PurgeClearedTrxOlderThanByDay.HasValue)
  331. {
  332. try
  333. {
  334. using (var dbContext = new SqliteDbContext())
  335. {
  336. var due = DateTime.Now.Subtract(new TimeSpan(appConfig.PurgeClearedTrxOlderThanByDay.Value, 0, 0, 0));
  337. var deleting = dbContext.PumpTransactionModels.Where(t => t.State == FuelSaleTransactionState.Cleared && t.PaidTime.HasValue && t.PaidTime <= due);
  338. dbContext.RemoveRange(deleting);
  339. var deletedRowCount = dbContext.SaveChanges();
  340. if (deletedRowCount > 0)
  341. fdcLogger.LogDebug($"PurgeClearedTrxOlderThanByDay purged: {deletedRowCount} rows");
  342. }
  343. }
  344. catch (Exception exxx)
  345. {
  346. fdcLogger.LogError("PurgeClearedTrxOlderThanByDay exceptioned: " + exxx.ToString());
  347. }
  348. try
  349. {
  350. if (appConfig.PurgePayableTrxOlderThanByMin.HasValue)
  351. {
  352. using (var dbContext = new SqliteDbContext())
  353. {
  354. var due = DateTime.Now.Subtract(new TimeSpan(0, appConfig.PurgePayableTrxOlderThanByMin.Value, 0));
  355. var deleting = dbContext.PumpTransactionModels.Where(t => t.State == FuelSaleTransactionState.Payable && t.SaleStartTime.HasValue && t.SaleStartTime <= due);
  356. dbContext.RemoveRange(deleting);
  357. var deletedRowCount = dbContext.SaveChanges();
  358. if (deletedRowCount > 0)
  359. fdcLogger.LogDebug($"PurgePayableTrxOlderThanByMin purged: {deletedRowCount} rows");
  360. }
  361. }
  362. }
  363. catch (Exception exxx)
  364. {
  365. fdcLogger.LogError("PurgePayableTrxOlderThanByMin exceptioned: " + exxx.ToString());
  366. }
  367. }
  368. };
  369. this.purgeDatabaseTimer.Start();
  370. }
  371. this.services = services;
  372. var loggerFactory = services.GetRequiredService<ILoggerFactory>();
  373. fdcLogger = loggerFactory.CreateLogger("DynamicPrivate_FdcServer");
  374. cloudLogger = loggerFactory.CreateLogger("CloudRestClient");
  375. //this.configurator = services.GetService<Configurator>();
  376. this.nozzleExtraInfos = appConfig.PumpConfigs
  377. ?.SelectMany(p => p.NozzleConfigs,
  378. (p, ns) =>
  379. new NozzleExtraInfo()
  380. {
  381. PumpId = p.PumpId,
  382. NozzleLogicalId = ns.NozzleLogicalId,
  383. SiteLevelNozzleId = ns.SiteLevelNozzleId,
  384. ProductBarcode = ns.ProductCode,
  385. ProductName = appConfig.ProductConfigs?.FirstOrDefault(p => p.ProductCode == ns.ProductCode)?.ProductName,
  386. TankNumber = ns.TankNumber,
  387. Description = ns.Description,
  388. });
  389. FdcResourceArbitrator.fdcLogger = fdcLogger;
  390. this.fdcServer = new FDCPOSInterfaceServer(services);
  391. }
  392. public FdcServerHostApp(string autoAuthCallingPumps, int maxStackUnpaidTrxPerPump, int listeningPort
  393. , IServiceProvider services)
  394. {
  395. //QRCoder.AsciiQRCode c = new QRCoder.AsciiQRCode();
  396. this.config_AutoAuthCallingPumps = autoAuthCallingPumps.ToLower() == "true" ? true : false;
  397. this.config_MaxStackUnpaidTrxPerPump = maxStackUnpaidTrxPerPump;
  398. this.config_ListeningPort = listeningPort;
  399. this.services = services;
  400. var loggerFactory = services.GetRequiredService<ILoggerFactory>();
  401. fdcLogger = loggerFactory.CreateLogger("DynamicPrivate_FdcServer");
  402. cloudLogger = loggerFactory.CreateLogger("CloudRestClient");
  403. this.configurator = services.GetService<Configurator>();
  404. this.nozzleExtraInfos = Configurator.Default.NozzleExtraInfoConfiguration.Mapping;
  405. FdcResourceArbitrator.fdcLogger = fdcLogger;
  406. this.fdcServer = new FDCPOSInterfaceServer(services);
  407. this.onConfiguratorConfigFileChangedEventHandler = (_, __) =>
  408. {
  409. try
  410. {
  411. var c = Configurator.Default.DeviceProcessorConfiguration.Processor
  412. .FirstOrDefault(p => p.Name == this.MetaConfigName);
  413. if (c == null) return;
  414. var newAutoAuthCallingPumps = c.Parameter.First(p => p.Name == "AutoAuthCallingPumps").Value.ToLower() == "true" ? true : false;
  415. var newMaxStackUnpaidTrxPerPump = int.Parse(c.Parameter.First(p => p.Name == "MaxStackUnpaidTrxPerPump").Value);
  416. if (this.config_AutoAuthCallingPumps != newAutoAuthCallingPumps)
  417. {
  418. fdcLogger.LogInformation("Configuration change detected on parameter: autoAuthCallingPumps, " +
  419. "will update its value from: " + this.config_AutoAuthCallingPumps + " to: " + newAutoAuthCallingPumps);
  420. this.config_AutoAuthCallingPumps = newAutoAuthCallingPumps;
  421. }
  422. if (this.config_MaxStackUnpaidTrxPerPump != newMaxStackUnpaidTrxPerPump)
  423. {
  424. fdcLogger.LogInformation("Configuration change detected on parameter: maxStackUnpaidTrxPerPump, " +
  425. "will update its value from: " + this.config_MaxStackUnpaidTrxPerPump + " to: " + newMaxStackUnpaidTrxPerPump);
  426. this.config_MaxStackUnpaidTrxPerPump = newMaxStackUnpaidTrxPerPump;
  427. }
  428. }
  429. catch (Exception exxx)
  430. {
  431. fdcLogger.LogError("Configuration auto reload exceptioned: " + exxx);
  432. }
  433. };
  434. this.configurator.OnConfigFileChanged += this.onConfiguratorConfigFileChangedEventHandler;
  435. }
  436. public void Init(IEnumerable<IProcessor> processors)
  437. {
  438. this.fdcPumpControllers = new List<IFdcPumpController>();
  439. this.fdcPumpControllers.AddRange(
  440. processors.WithHandlerOrApp<IFdcPumpController>().SelectHandlerOrAppThenCast<IFdcPumpController>());
  441. foreach (var p in processors)
  442. {
  443. if (p.IsWithHandlerOrApp<IEnumerable<IFdcPumpController>>())
  444. {
  445. var pumpGroupHandler = p.SelectHandlerOrAppThenCast<IEnumerable<IFdcPumpController>>();
  446. this.fdcPumpControllers.AddRange(pumpGroupHandler);
  447. // dynamic check does `OnFdcServerInit` defined
  448. if (pumpGroupHandler.GetType().GetMethod("OnFdcServerInit")?.GetParameters()
  449. ?.FirstOrDefault()?.ParameterType?.IsAssignableFrom(typeof(Dictionary<string, object>)) ?? false)
  450. {
  451. pumpGroupHandler.GetType().GetMethod("OnFdcServerInit")?.Invoke(
  452. pumpGroupHandler,
  453. new[]{
  454. new Dictionary<string, object>() {
  455. { "NozzleProductMapping", this.nozzleExtraInfos }
  456. }
  457. });
  458. }
  459. }
  460. }
  461. if (this.fdcPumpControllers.GroupBy(p => p.PumpId).Any(g => g.Count() > 1))
  462. throw new ArgumentException("Duplicate PumpId in Fdc Pump Controllers, PumpId must be unique");
  463. this.fdcCommunicableControllers = new List<IFdcCommunicableController>();
  464. this.fdcCommunicableControllers.AddRange(processors.WithHandlerOrApp<IFdcCommunicableController>().SelectHandlerOrAppThenCast<IFdcCommunicableController>());
  465. this.autoTankGaugeControllers = new List<IAutoTankGaugeController>();
  466. this.autoTankGaugeControllers.AddRange(processors.WithHandlerOrApp<IAutoTankGaugeController>().SelectHandlerOrAppThenCast<IAutoTankGaugeController>());
  467. }
  468. public class Grouping<TKey, TElement> : List<TElement>, IGrouping<TKey, TElement>
  469. {
  470. public Grouping(TKey key) : base() => Key = key;
  471. public Grouping(TKey key, int capacity) : base(capacity) => Key = key;
  472. public Grouping(TKey key, IEnumerable<TElement> collection)
  473. : base(collection) => Key = key;
  474. public TKey Key { get; private set; }
  475. }
  476. public Task<bool> Start()
  477. {
  478. //var fdcServer = new FDCPOSInterfaceServer();
  479. #region OnGetCountrySettings, OnLogOnReq and OnLogOffReq
  480. fdcServer.OnGetCountrySettingsReq += (string workstationID, string applicationSender, int requestID) =>
  481. {
  482. fdcLogger.LogDebug("OnGetCountrySettingsReq (wid: " + workstationID + ", appSender: " + applicationSender + ", requestId: " + requestID + ")");
  483. fdcServer.GetCountrySettings(workstationID, applicationSender, requestID, "Litre", "CNY", "Litre", "C", "3", ".", "CHN", "zh-cn", (int)ErrorCode.ERRCD_OK, OverallResult.Success.ToString());
  484. };
  485. fdcServer.OnStartForecourtReq += (string workstationID, string applicationSender, int requestID) =>
  486. {
  487. fdcLogger.LogDebug("OnStartForecourtReq (wid: " + workstationID + ", appSender: " + applicationSender + ", requestId: " + requestID + ")");
  488. fdcServer.StartForecourt(workstationID, applicationSender, requestID, (int)ErrorCode.ERRCD_OK, OverallResult.Success.ToString());
  489. };
  490. fdcServer.OnLogOnReq += (string workstationID, string applicationSender, int requestId, int responsePort, int unsolicitedPort, int version) =>
  491. {
  492. fdcLogger.LogInformation("OnLogOnReq(wid: " + workstationID + ", appSender: " + applicationSender + ", requestId: " + requestId + ", ver: " + version + ")");
  493. fdcServer.LogOn(workstationID, applicationSender, requestId, responsePort, unsolicitedPort, (int)ErrorCode.ERRCD_OK, OverallResult.Success.ToString());
  494. };
  495. fdcServer.OnLogOffReq += (string workstationID, string applicationSender, int requestId) =>
  496. {
  497. fdcLogger.LogInformation("OnLogOffReq(wid: " + workstationID + ", appSender: " + applicationSender + ", requestId: " + requestId + ")");
  498. fdcServer.LogOff(workstationID, applicationSender, requestId, (int)ErrorCode.ERRCD_OK, OverallResult.Success.ToString());
  499. };
  500. #endregion
  501. #region reserve related
  502. fdcServer.OnLockNozzleReq += async (string workstationID, string applicationSender, int requestId, int deviceId, int NozzleNo) =>
  503. {
  504. try
  505. {
  506. fdcLogger.LogDebug("OnLockNozzleReq (wid: " + workstationID + ", appSender: "
  507. + applicationSender + ", requestId: " + requestId
  508. + ", deviceId: " + deviceId + ", NozzleNo: " + NozzleNo);
  509. var result = await this.LockNozzleAndNotifyAllFdcClientsAsync(int.Parse(workstationID), applicationSender, requestId, deviceId, NozzleNo);
  510. if (!result)
  511. fdcLogger.LogInformation(" LockNozzle failed");
  512. fdcLogger.LogDebug(" OnLockNozzleReq done");
  513. }
  514. catch (Exception exxx)
  515. {
  516. fdcLogger.LogError("OnLockNozzleReq exceptioned: " + exxx.ToString());
  517. }
  518. };
  519. fdcServer.OnUnlockNozzleReq += async (string workstationID, string applicationSender, int requestId, int deviceId, int NozzleNo) =>
  520. {
  521. try
  522. {
  523. fdcLogger.LogDebug("OnUnlockNozzleReq (wid: " + workstationID + ", appSender: "
  524. + applicationSender + ", requestId: " + requestId
  525. + ", deviceId: " + deviceId + ", NozzleNo: " + NozzleNo);
  526. var result = await this.UnlockNozzleAndNotifyAllFdcClientsAsync(int.Parse(workstationID), applicationSender, requestId, deviceId, NozzleNo);
  527. //if (!result)
  528. // fdcServer.UnlockNozzle(workstationID, applicationSender, requestId, deviceId, deviceId, NozzleNo, (int)ErrorCode.ERRCD_OK, OverallResult.Success.ToString());
  529. //else
  530. // fdcServer.UnlockNozzle(workstationID, applicationSender, requestId, deviceId, deviceId, NozzleNo, (int)ErrorCode.ERRCD_OK, OverallResult.Failure.ToString());
  531. fdcLogger.LogDebug(" OnUnlockNozzleReq done");
  532. }
  533. catch (Exception exxx)
  534. {
  535. fdcLogger.LogError("OnUnlockNozzleReq exceptioned: " + exxx.ToString());
  536. }
  537. };
  538. fdcServer.OnReserveFuelPointReq += async (string workstationID, string applicationSender, int requestId, int deviceId) =>
  539. {
  540. try
  541. {
  542. fdcLogger.LogDebug("OnReserveFuelPointReq (wid: " + workstationID + ", appSender: " + applicationSender + ", requestId: " + requestId + ", deviceId: " + deviceId);
  543. var result = await FdcResourceArbitrator.Default.TryReserveFuelPointAsync(int.Parse(workstationID), deviceId);
  544. if (result)
  545. fdcServer.ReserveFuelPoint(workstationID, applicationSender, requestId, deviceId, deviceId, (int)ErrorCode.ERRCD_OK, OverallResult.Success.ToString());
  546. else
  547. {
  548. fdcLogger.LogInformation(" ReserveFuelPoint failed");
  549. fdcServer.ReserveFuelPoint(workstationID, applicationSender, requestId, deviceId, deviceId, (int)ErrorCode.ERRCD_FPLOCK, OverallResult.Failure.ToString());
  550. }
  551. fdcLogger.LogDebug(" OnReserveFuelPointReq done");
  552. }
  553. catch (Exception exxx)
  554. {
  555. fdcLogger.LogError("OnReserveFuelPointReq exceptioned: " + exxx.ToString());
  556. fdcServer.ReserveFuelPoint(workstationID, applicationSender, requestId, deviceId, deviceId, (int)ErrorCode.ERRCD_OK, OverallResult.Failure.ToString());
  557. }
  558. };
  559. fdcServer.OnFreeFuelPointReq += async (string workstationID, string applicationSender, int requestId, int deviceId) =>
  560. {
  561. try
  562. {
  563. fdcLogger.LogDebug("OnFreeFuelPointReq (wid: " + workstationID + ", appSender: " + applicationSender + ", requestId: " + requestId + ", deviceId: " + deviceId);
  564. var result = await FdcResourceArbitrator.Default.TryUnreserveFuelPointAsync(int.Parse(workstationID), deviceId);
  565. if (result)
  566. fdcServer.FreeFuelPoint(workstationID, applicationSender, requestId, deviceId, deviceId, (int)ErrorCode.ERRCD_OK, OverallResult.Success.ToString());
  567. else
  568. fdcServer.FreeFuelPoint(workstationID, applicationSender, requestId, deviceId, deviceId, (int)ErrorCode.ERRCD_OK, OverallResult.Failure.ToString());
  569. fdcLogger.LogDebug(" OnFreeFuelPointReq done");
  570. }
  571. catch (Exception exxx)
  572. {
  573. fdcLogger.LogError("OnFreeFuelPointReq exceptioned: " + exxx.ToString());
  574. fdcServer.FreeFuelPoint(workstationID, applicationSender, requestId, deviceId, deviceId, (int)ErrorCode.ERRCD_NOTPOSSIBLE, OverallResult.Failure.ToString());
  575. }
  576. };
  577. fdcServer.OnLockFuelSaleTrxReq += async (string workstationID, string applicationSender, int requestId, int deviceId,
  578. int transactionNo, string releaseToken) =>
  579. {
  580. try
  581. {
  582. fdcLogger.LogDebug("OnLockFuelSaleTrxReq (wid: " + workstationID + ", appSender: " + applicationSender + ", requestId: "
  583. + requestId + ", deviceId: " + deviceId + ", transactionNo: " + transactionNo + ", releaseToken: " + releaseToken + ")");
  584. var result = await this.LockFuelSaleTrxAndNotifyAllFdcClientsAsync(int.Parse(workstationID), applicationSender, requestId, deviceId, transactionNo, int.Parse(releaseToken));
  585. //var result = this.LockFuelSaleTrxAndNotifyAllFdcClients(int.Parse(workstationID), deviceId, transactionNo, int.Parse(releaseToken));
  586. //if (result != null)
  587. //{
  588. // var targetController = fdcPumpControllers
  589. // .First(c => c.PumpId == deviceId) as IFdcPumpController;
  590. // fdcServer.LockFuelSaleTrx(workstationID, applicationSender, requestId, deviceId,
  591. // transactionNo, releaseToken, (int)ErrorCode.ERRCD_OK, 1, OverallResult.Success.ToString());
  592. // fdcServer.FuelSaleTrx(deviceId,
  593. // result.Volumn / Math.Pow(10, targetController.VolumeDecimalDigits),
  594. // result.Amount / Math.Pow(10, targetController.AmountDecimalDigits),
  595. // result.UnitPrice / Math.Pow(10, targetController.PriceDecimalDigits),
  596. // result.LogicalNozzleId,
  597. // int.Parse(result.ProductBarcode),
  598. // "refer cloud" + result.ProductBarcode, "", 1,
  599. // int.Parse(result.TransactionSeqNumberFromPhysicalPump),
  600. // (int)FuelSaleTransactionState.Locked,
  601. // 0, releaseToken,
  602. // result.SaleStartTime?.ToString("yyyy-MM-dd HH:mm:ss"),
  603. // result.SaleEndTime?.ToString("yyyy-MM-dd HH:mm:ss"),
  604. // workstationID, "", int.Parse(workstationID), 0);
  605. //}
  606. //else
  607. //{
  608. // fdcServer.LockFuelSaleTrx(workstationID, applicationSender, requestId, deviceId,
  609. // transactionNo, releaseToken, (int)ErrorCode.ERRCD_TRANSLOCKED, 1, OverallResult.Failure.ToString());
  610. //}
  611. if (result == null)
  612. fdcLogger.LogDebug($" OnLockFuelSaleTrxReq(releaseToken: {releaseToken}) failed");
  613. fdcLogger.LogDebug($" OnLockFuelSaleTrxReq(releaseToken: {releaseToken}) done");
  614. }
  615. catch (Exception exxx)
  616. {
  617. fdcLogger.LogError("OnLockFuelSaleTrxReq exceptioned: " + exxx.ToString());
  618. try
  619. {
  620. fdcServer.LockFuelSaleTrx(workstationID, applicationSender, requestId, deviceId,
  621. transactionNo, releaseToken, (int)ErrorCode.ERRCD_TRANSLOCKED, 1, OverallResult.Failure.ToString());
  622. }
  623. catch (Exception eeee)
  624. {
  625. fdcLogger.LogError("Fdc server is trying to respond a failed LocFuelSaleTrx response to a fdcclient that issue current LockFuleSaleTrx request, but still failed with reason: " + eeee);
  626. }
  627. }
  628. };
  629. fdcServer.OnUnlockFuelSaleTrxReq += async (string workstationID, string applicationSender, int requestId, int deviceId,
  630. int transactionNo, string releaseToken) =>
  631. {
  632. try
  633. {
  634. fdcLogger.LogDebug("OnUnlockFuelSaleTrxReq (wid: " + workstationID + ", appSender: " + applicationSender
  635. + ", requestId: " + requestId + ", deviceId: " + deviceId + ", transactionNo: " + transactionNo
  636. + ", releaseToken: " + releaseToken);
  637. var result = await this.UnlockFuelSaleTrxAndNotifyAllFdcClientsAsync(int.Parse(workstationID), applicationSender, requestId, deviceId, transactionNo, int.Parse(releaseToken));
  638. //var result = this.UnlockFuelSaleTrxAndNotifyAllFdcClients(int.Parse(workstationID), deviceId, transactionNo, int.Parse(releaseToken));
  639. //if (result != null)
  640. //{
  641. // var targetController = fdcPumpControllers
  642. // .First(c => c.PumpId == deviceId) as IFdcPumpController;
  643. // fdcServer.UnlockFuelSaleTrx(workstationID, applicationSender, requestId, deviceId,
  644. // transactionNo, releaseToken, (int)ErrorCode.ERRCD_OK, 1, OverallResult.Success.ToString());
  645. // fdcServer.FuelSaleTrx(deviceId,
  646. // result.Volumn / Math.Pow(10, targetController.VolumeDecimalDigits),
  647. // result.Amount / Math.Pow(10, targetController.AmountDecimalDigits),
  648. // result.UnitPrice / Math.Pow(10, targetController.PriceDecimalDigits),
  649. // result.LogicalNozzleId,
  650. // int.Parse(result.ProductBarcode),
  651. // "refer cloud" + result.ProductBarcode, "", 1,
  652. // int.Parse(result.TransactionSeqNumberFromPhysicalPump),
  653. // (int)FuelSaleTransactionState.Payable,
  654. // 0, releaseToken,
  655. // result.SaleStartTime?.ToString("yyyy-MM-dd HH:mm:ss"),
  656. // result.SaleEndTime?.ToString("yyyy-MM-dd HH:mm:ss"),
  657. // "", "", int.Parse(workstationID), 0);
  658. //}
  659. //else
  660. //{
  661. // fdcServer.UnlockFuelSaleTrx(workstationID, applicationSender, requestId, deviceId,
  662. // transactionNo, releaseToken, (int)ErrorCode.ERRCD_TRANSLOCKED, 1, OverallResult.Failure.ToString());
  663. //}
  664. if (result == null)
  665. fdcLogger.LogDebug(" OnUnlockFuelSaleTrxReq failed");
  666. fdcLogger.LogDebug(" OnUnlockFuelSaleTrxReq done");
  667. }
  668. catch (Exception exxx)
  669. {
  670. fdcLogger.LogError("OnUnlockFuelSaleTrxReq exceptioned: " + exxx.ToString() + Environment.NewLine);
  671. try
  672. {
  673. fdcServer.UnlockFuelSaleTrx(workstationID, applicationSender, requestId, deviceId,
  674. transactionNo, releaseToken, (int)ErrorCode.ERRCD_TRANSLOCKED, 1, OverallResult.Failure.ToString());
  675. }
  676. catch (Exception eeee)
  677. {
  678. fdcLogger.LogError("Fdc server is trying to respond a failed UnlocFuelSaleTrx response to a fdcclient that issue current UnlockFuleSaleTrx request, but still failed with reason: " + eeee);
  679. }
  680. }
  681. };
  682. #endregion
  683. fdcServer.OnGetTankDataReq += async (string workstationID, string applicationSender, int requestID, int deviceId, int tankNo) =>
  684. {
  685. try
  686. {
  687. fdcLogger.LogDebug("OnGetTankDataReq (wid: " + workstationID + ", appSender: " + applicationSender + ", requestId: " + requestID + ", deviceId: " + deviceId + ", tankNo: " + tankNo + ")");
  688. if (this.autoTankGaugeControllers == null || !this.autoTankGaugeControllers.Any())
  689. {
  690. fdcLogger.LogDebug($" failed for OnGetTankDataReq since there's an empty AutoTankGaugeControllers list.");
  691. fdcServer.GetTankDataSend(workstationID, applicationSender, requestID,
  692. (int)ErrorCode.ERRCD_NOTPOSSIBLE, OverallResult.DeviceUnavailable.ToString());
  693. return;
  694. }
  695. var atgController = this.autoTankGaugeControllers.FirstOrDefault();
  696. if (!atgController.Tanks.Any())
  697. {
  698. fdcLogger.LogDebug($" failed for OnGetTankDataReq since there's no Tanks at all in AutoTankGaugeController.");
  699. fdcServer.GetTankDataSend(workstationID, applicationSender, requestID,
  700. (int)ErrorCode.ERRCD_NOTPOSSIBLE, OverallResult.DeviceUnavailable.ToString());
  701. return;
  702. }
  703. if (deviceId != -1
  704. && !atgController.Tanks.Any(t => t.TankNumber == deviceId))
  705. {
  706. fdcLogger.LogDebug($" failed for OnGetTankDataReq since there's no Tank with number: {deviceId} in AutoTankGaugeController.");
  707. fdcServer.GetTankDataSend(workstationID, applicationSender, requestID,
  708. (int)ErrorCode.ERRCD_NOTPOSSIBLE, OverallResult.DeviceUnavailable.ToString());
  709. return;
  710. }
  711. //fdcLogger.LogDebug($" atgController has {atgController.Tanks?.Count() ?? -1} tanks configed.");
  712. if (deviceId == -1)
  713. {
  714. foreach (var tank in atgController.Tanks)
  715. {
  716. try
  717. {
  718. var tankReading = await atgController.GetTankReadingAsync(tank.TankNumber);
  719. fdcLogger.LogDebug($" GetTankReadingAsync for tankNumber: {tank.TankNumber} succeed, data-> { tankReading.ToString()}");
  720. fdcServer.GetTankDataAdd(workstationID, applicationSender, requestID,
  721. tank.TankNumber,
  722. tank.TankNumber, 0, 0,
  723. tankReading.Height ?? 0,
  724. tankReading.Volume ?? 0,
  725. tankReading.TcVolume ?? 0,
  726. tankReading.Temperature ?? 0,
  727. tankReading.Water ?? 0,
  728. 0,
  729. (int)ErrorCode.ERRCD_OK, (int)LogicalDeviceState.FDC_READY);
  730. }
  731. catch (Exception exx)
  732. {
  733. fdcLogger.LogInformation($" failed for GetTankReadingAsync for tankNumber: {tank.TankNumber} by exception: {exx}" +
  734. $"{Environment.NewLine} will skip this tank and continue on next tank...");
  735. continue;
  736. }
  737. }
  738. }
  739. else
  740. {
  741. var tankReading = await atgController.GetTankReadingAsync(deviceId);
  742. fdcLogger.LogDebug($" GetTankReadingAsync for tankNumber: " + deviceId + " succeed " +
  743. Environment.NewLine + tankReading.ToString());
  744. fdcServer.GetTankDataAdd(workstationID, applicationSender, requestID,
  745. deviceId,
  746. deviceId, 0, 0,
  747. tankReading.Height ?? 0,
  748. tankReading.Volume ?? 0,
  749. tankReading.TcVolume ?? 0,
  750. tankReading.Temperature ?? 0,
  751. tankReading.Water ?? 0,
  752. 0,
  753. (int)ErrorCode.ERRCD_OK, (int)LogicalDeviceState.FDC_READY);
  754. }
  755. fdcServer.GetTankDataSend(workstationID, applicationSender, requestID, (int)ErrorCode.ERRCD_OK, OverallResult.Success.ToString());
  756. }
  757. catch (Exception exxx)
  758. {
  759. fdcLogger.LogError("OnGetTankDataReq exceptioned: " + exxx.ToString());
  760. fdcServer.GetTankDataSend(workstationID, applicationSender, requestID,
  761. (int)ErrorCode.ERRCD_BADVAL, OverallResult.Failure.ToString());
  762. }
  763. };
  764. fdcServer.OnGetProductTableReq += (string workstationID, string applicationSender, int requestID) =>
  765. {
  766. fdcLogger.LogDebug("OnGetProductTableReq (wid: " + workstationID + ", appSender: " + applicationSender + ", requestId: " + requestID + ")");
  767. var allProductBarcodes = nozzleExtraInfos.GroupBy(p => p.ProductBarcode);
  768. foreach (var b in allProductBarcodes)
  769. fdcServer.GetProductTableAdd(workstationID, applicationSender, requestID, b.Key, (b.FirstOrDefault()?.ProductName ?? ("refer cloud for name of " + b.Key)));
  770. fdcServer.GetProductTableSend(workstationID, applicationSender, requestID, (int)ErrorCode.ERRCD_OK, OverallResult.Success.ToString());
  771. };
  772. fdcServer.OnGetConfigurationReq += (string workstationID, string applicationSender, int requestId, string deviceType) =>
  773. {
  774. lock (this.syncObject)
  775. {
  776. try
  777. {
  778. fdcLogger.LogDebug("OnGetConfigurationReq (wid: " + workstationID + ", appSender: " + applicationSender + ", requestId: " + requestId + ", deviceType: " + deviceType);
  779. if (deviceType == "TLG")
  780. {
  781. /* here based on local manual config to generate tanks and return to POS side, probly use the concrete AutoTankGaugeControl.Tanks
  782. * is a better choice but the known issue is the product code and label read from VR ATG console may not correct(may bug in VR console).
  783. * so keep this way of local manual config with high priority.
  784. */
  785. var tankGroups = this.nozzleExtraInfos.Where(np => np.TankNumber.HasValue)
  786. .GroupBy(np => np.TankNumber)
  787. .Select(tg =>
  788. {
  789. /* if real ATG exists, will use ATG Tank info, otherwise create a logical Tank object but fill with info from nozzleExtraInfo*/
  790. var tank = this.autoTankGaugeControllers?.SelectMany(c => c.Tanks ?? new Tank[] { })?.FirstOrDefault(t => t.TankNumber == tg.Key)
  791. ?? new Tank()
  792. {
  793. TankNumber = (byte)tg.Key.Value,
  794. Label = "Logical tank",
  795. Product = new Product()
  796. {
  797. ProductCode = tg.First().ProductBarcode.ToString(),
  798. ProductLabel = tg.First().Description
  799. }
  800. };
  801. var pumpGroups = tg.GroupBy(np => np.PumpId).Select(pg => new Grouping<int, byte>(pg.Key, pg.Select(x => (byte)x.NozzleLogicalId)));
  802. return new Grouping<Tank, Grouping<int, byte>>(tank, pumpGroups);
  803. });
  804. if (!tankGroups.Any())
  805. {
  806. fdcServer.GetConfigurationSend(workstationID, applicationSender, requestId,
  807. deviceType, OverallResult.WrongConfiguration.ToString());
  808. fdcLogger.LogDebug(" OnGetConfigurationReq with TLG done with WrongConfiguration as no tanks info generated.");
  809. return;
  810. }
  811. fdcLogger.LogDebug(" OnGetConfigurationReq with TLG done with success, returned data-> " +
  812. tankGroups.Select(tg => $"Tank with number: {tg.Key.TankNumber}, " +
  813. $"label: {tg.Key.Label ?? ""}, productCode: {tg.Key.Product?.ProductCode ?? ""} linked to PumpIds: " +
  814. $"{tg.Select(p => $"{p.Key}(nzlLogiIds: {p.Select(n => n.ToString()).Aggregate("", (acc, n) => acc + ", " + n)})").Aggregate("", (acc, n) => acc + ", " + n)}")
  815. .Aggregate((acc, n) => acc + "; " + n));
  816. fdcServer.GetConfigurationAddTLG(workstationID, applicationSender, requestId, tankGroups);
  817. }
  818. else
  819. {
  820. foreach (var fdcPumpController in fdcPumpControllers)
  821. {
  822. foreach (var nozzle in fdcPumpController.Nozzles)
  823. {
  824. var bindProduct = nozzleExtraInfos.FirstOrDefault(n =>
  825. n.PumpId == fdcPumpController.PumpId
  826. && n.NozzleLogicalId == nozzle.LogicalId);
  827. var fuelPrice = nozzle.RealPriceOnPhysicalPump == null ? 0 :
  828. (nozzle.RealPriceOnPhysicalPump.Value / Math.Pow(10, fdcPumpController.PriceDecimalDigits));
  829. if (string.IsNullOrEmpty(deviceType) || deviceType == "DSP")
  830. {
  831. int bindProductNo = (bindProduct == null ? 0 : bindProduct.ProductBarcode);
  832. if (bindProduct == null)
  833. fdcLogger.LogInformation("Could not find bind product for pumpId: " + fdcPumpController.PumpId + ", nozzleId: " + nozzle.LogicalId + ", will use 0 instead");
  834. fdcLogger.LogDebug(" OnGetConfigurationReq pumpId: "
  835. + fdcPumpController.PumpId
  836. + ", nozzle logicalId: " + nozzle.LogicalId + ", nozzle phyId: " + nozzle.PhysicalId + ", productNo: " + bindProductNo
  837. + ", price: " + fuelPrice);
  838. fdcServer.GetConfigurationAddDSP(workstationID, applicationSender, requestId,
  839. fdcPumpController.PumpId,
  840. bindProductNo,
  841. bindProduct?.ProductName ?? ("refer cloud for name of " + bindProductNo),
  842. 1,
  843. fuelPrice);
  844. fdcServer.GetConfigurationAddFP(workstationID, applicationSender, requestId,
  845. fdcPumpController.PumpId,
  846. fdcPumpController.PumpId, nozzle.LogicalId, (bindProduct == null ? 0 : bindProduct.ProductBarcode), 0, 100, 0);
  847. }
  848. //if (string.IsNullOrEmpty(deviceType) || deviceType == "PP")
  849. // fdcServer.GetConfigurationAddPP(workstationID, applicationSender, requestId, fdcPumpController.PumpId);
  850. }
  851. }
  852. }
  853. fdcServer.GetConfigurationSend(workstationID, applicationSender, requestId,
  854. deviceType, OverallResult.Success.ToString());
  855. fdcLogger.LogDebug(" OnGetConfigurationReq done");
  856. }
  857. catch (Exception exxx)
  858. {
  859. fdcLogger.LogError("OnGetConfigurationReq exceptioned: " + exxx.ToString());
  860. fdcServer.GetConfigurationSend(workstationID, applicationSender, requestId, deviceType, OverallResult.Failure.ToString());
  861. }
  862. }
  863. };
  864. fdcServer.OnGetDeviceStateReq += async (string workstationID, string applicationSender, int requestId, string deviceType, int deviceId) =>
  865. {
  866. try
  867. {
  868. fdcLogger.LogDebug("OnGetDeviceStateReq (wid: " + workstationID + ", appSender: " + applicationSender + ", requestId: " + requestId + ", deviceId: " + deviceId + ")");
  869. // -1 indicates query all pumps
  870. if (deviceId == -1)
  871. {
  872. var controllers = fdcPumpControllers;
  873. foreach (var c in controllers)
  874. {
  875. var s = await c.QueryStatusAsync();
  876. fdcLogger.LogDebug(" Pump with pumpId: " + c.PumpId + " is in state: " + s.ToString()
  877. + ", nozzles states are(LogicalId-State): "
  878. + (c.Nozzles != null ?
  879. (c.Nozzles.Any() ?
  880. c.Nozzles.Select(n =>
  881. n.LogicalId.ToString() + "-" + (n.LogicalState?.ToString() ?? ""))
  882. .Aggregate((n, acc) => n + ", " + acc) : "") : ""));
  883. byte nozzleLockedOrUnlockedBitMap = 0;
  884. foreach (var nozzle in c.Nozzles)
  885. {
  886. if (nozzle.LogicalState.HasValue && nozzle.LogicalState == LogicalDeviceState.FDC_LOCKED)
  887. nozzleLockedOrUnlockedBitMap = SetBit(nozzleLockedOrUnlockedBitMap, nozzle.LogicalId - 1, nozzle.LogicalId, 1);
  888. else
  889. nozzleLockedOrUnlockedBitMap = SetBit(nozzleLockedOrUnlockedBitMap, nozzle.LogicalId - 1, nozzle.LogicalId, 0);
  890. }
  891. fdcServer.GetDeviceStateAdd(workstationID, applicationSender, requestId, deviceType, c.PumpId, (int)s,
  892. -1, "", "", (int)FDCLogicalState.FDC_LOGICAL_ST_UNLOCKED, "",
  893. c.Nozzles.Count(), 0, nozzleLockedOrUnlockedBitMap, 0);
  894. }
  895. }
  896. else
  897. {
  898. var targetController = fdcPumpControllers.First(c => c.PumpId == deviceId) as IFdcPumpController;
  899. byte nozzleLockedOrUnlockedBitMap = 0;
  900. foreach (var nozzle in targetController.Nozzles)
  901. {
  902. if (nozzle.LogicalState.HasValue && nozzle.LogicalState == LogicalDeviceState.FDC_LOCKED)
  903. nozzleLockedOrUnlockedBitMap = SetBit(nozzleLockedOrUnlockedBitMap, nozzle.LogicalId - 1, nozzle.LogicalId, 1);
  904. else
  905. nozzleLockedOrUnlockedBitMap = SetBit(nozzleLockedOrUnlockedBitMap, nozzle.LogicalId - 1, nozzle.LogicalId, 0);
  906. }
  907. fdcServer.GetDeviceStateAdd(workstationID, applicationSender, requestId, deviceType, deviceId,
  908. (int)targetController.QueryStatusAsync().Result,
  909. -1, "", "", (int)FDCLogicalState.FDC_LOGICAL_ST_UNLOCKED, "",
  910. targetController.Nozzles.Count(),
  911. 0, nozzleLockedOrUnlockedBitMap, 0);
  912. }
  913. fdcServer.GetDeviceStateSend(workstationID, applicationSender, requestId, (int)ErrorCode.ERRCD_OK, OverallResult.Success.ToString());
  914. fdcLogger.LogDebug(" OnGetDeviceStateReq done");
  915. }
  916. catch (Exception exxx)
  917. {
  918. fdcLogger.LogError("OnGetDeviceStateReq exceptioned: " + exxx.ToString());
  919. fdcServer.GetDeviceStateSend(workstationID, applicationSender, requestId, (int)ErrorCode.ERRCD_BADCONF, OverallResult.Failure.ToString());
  920. }
  921. };
  922. fdcServer.OnAuthoriseFuelPointReq += async
  923. (string workstationID, string applicationSender, int requestId, string releaseToken, int fuellingType,
  924. int deviceId, int reservingDeviceId, double maxTrxAmount, double maxTrxVolume,
  925. string products, int mode, bool lockFuelSaleTrx, string payType) =>
  926. {
  927. //enum DeviceStatus
  928. //{
  929. // FDC_ST_NOTHING = -1,
  930. // FDC_ST_CONFIGURE = 0,
  931. // FDC_ST_DISABLED = 1,
  932. // FDC_ST_ERRORSTATE = 2,
  933. // FDC_ST_FUELLING = 3,
  934. // FDC_ST_INVALIDSTATE = 4,
  935. // FDC_ST_LOCKED = 5,
  936. // FDC_ST_OFFLINE = 6,
  937. // FDC_ST_OUTOFORDER = 7,
  938. // FDC_ST_READY = 8,
  939. // FDC_ST_REQUESTED = 9,
  940. // FDC_ST_STARTED = 10,
  941. // FDC_ST_SUSPENDED = 11,
  942. // FDC_ST_CALLING = 12,
  943. // FDC_ST_TEST = 13,
  944. // FDC_ST_SUSPENDED_STARTING = 14, // Added in FDC version 00.05
  945. // FDC_ST_SUSPENDED_FUELLING = 15,
  946. // FDC_ST_CLOSED = 16,
  947. // FDC_ST_AUTHORISED = 17, // Added in FDC version 00.07
  948. //};
  949. try
  950. {
  951. fdcLogger.LogDebug("OnAuthoriseFuelPointReq(wid: " + workstationID + ", appSender: " + applicationSender
  952. + ", requestId: " + requestId + ", releaseToken: " + releaseToken
  953. + ", deviceId: " + deviceId + ", reservingDeviceId: " + reservingDeviceId + ", maxTrxAmount: "
  954. + maxTrxAmount + ", maxTrxVolume: " + maxTrxVolume + ", products: " + products
  955. + ", lockFuelSaleTrx: " + lockFuelSaleTrx + ", payType: " + payType + ")");
  956. //if (targetController.QueryStatus() == LogicalDeviceState.FDC_FUELLING)
  957. //{
  958. //fdcLogger.LogDebug(" Pump: " + targetController.PumpId + " is in fueling so re-authorising request will be interpreted as RoundUpByAmount request");
  959. //var result = targetController.FuelingRoundUpByAmount();
  960. //fdcLogger.LogDebug(" Pump: " + targetController.PumpId + " RoundUpByAmount returned with: " + result);
  961. //fdcServer.AuthoriseFuelPoint(workstationID, applicationSender, requestId, -99, releaseToken, deviceId,
  962. // (int)LogicalDeviceState.FDC_READY, (int)ErrorCode.ERRCD_OK, OverallResult.Success.ToString());
  963. //return;
  964. //}
  965. //if (!string.IsNullOrEmpty(maxStackUnpaidTrxConfig))
  966. //{
  967. // SqliteDbContext dbContext = new SqliteDbContext();
  968. // var unpaidTrxCount = dbContext.PumpTransactionModels.Count(t => t.PumpId == targetController.PumpId && t.State == FuelSaleTransactionState.Payable);
  969. // if (unpaidTrxCount >= int.Parse(maxStackUnpaidTrxConfig))
  970. // {
  971. // fdcLogger.LogInformation(" Authorizing failed due to pump: " + targetController.PumpId + " have: " + unpaidTrxCount + " unpaid trx");
  972. // fdcServer.AuthoriseFuelPoint(workstationID, applicationSender, requestId, -99, releaseToken, deviceId,
  973. // (int)LogicalDeviceState.FDC_READY, (int)ErrorCode.ERRCD_MAXSTACKLIMIT, OverallResult.Failure.ToString());
  974. // return;
  975. // }
  976. //}
  977. var succeed = await this.AuthorizePumpAsync(deviceId, maxTrxAmount, maxTrxVolume);
  978. if (succeed)
  979. fdcServer.AuthoriseFuelPoint(workstationID, applicationSender, requestId, -99, releaseToken, deviceId,
  980. (int)LogicalDeviceState.FDC_READY, (int)ErrorCode.ERRCD_OK, OverallResult.Success.ToString());
  981. else
  982. {
  983. fdcLogger.LogError("Authorising FP(requestId: " + requestId + ", releaseToken: " + releaseToken + ", deviceId: " + deviceId
  984. + ", reservingDeviceId: " + reservingDeviceId + ", maxTrxAmount: " + maxTrxAmount + ", maxTrxVolume: " + maxTrxVolume
  985. + ", products: " + products + ", lockFuelSaleTrx: " + lockFuelSaleTrx + ", payType: " + payType + ") failed");
  986. fdcServer.AuthoriseFuelPoint(workstationID, applicationSender, requestId, -99, releaseToken, deviceId,
  987. (int)LogicalDeviceState.FDC_READY, (int)ErrorCode.ERRCD_NOPERM, OverallResult.Failure.ToString());
  988. }
  989. fdcLogger.LogDebug(" Authorising FP done");
  990. }
  991. catch (Exception exxx)
  992. {
  993. fdcLogger.LogError("OnAuthoriseFuelPointReq exceptioned: " + exxx.ToString());
  994. fdcServer.AuthoriseFuelPoint(workstationID, applicationSender, requestId, -99, releaseToken, deviceId,
  995. (int)LogicalDeviceState.FDC_READY, (int)ErrorCode.ERRCD_NOPERM, OverallResult.Failure.ToString());
  996. }
  997. };
  998. fdcServer.OnTerminateFuellingReq += async (string workstationID, string applicationSender, int requestId, int deviceId) =>
  999. {
  1000. fdcLogger.LogInformation("OnTerminateFuellingReq(wid: " + workstationID + ", appSender: " + applicationSender
  1001. + ", requestId: " + requestId + ", deviceId: " + deviceId + ")");
  1002. var targetController = fdcPumpControllers.FirstOrDefault(c => c.PumpId == deviceId);
  1003. if (targetController == null)
  1004. {
  1005. fdcServer.TerminateFuellingSend(workstationID, applicationSender, requestId, (int)ErrorCode.ERRCD_BADDEVID, OverallResult.WrongDeviceNo.ToString());
  1006. return;
  1007. }
  1008. try
  1009. {
  1010. /* interpreted as UnAuthorize has no special reason but for a rush request from customer */
  1011. fdcLogger.LogInformation(" OnTerminateFuellingReq will be interpreted as UnAuthorize to pump handler.");
  1012. var result = await targetController.UnAuthorizeAsync(1);
  1013. if (result)
  1014. fdcServer.TerminateFuellingSend(workstationID, applicationSender, requestId, (int)ErrorCode.ERRCD_OK, OverallResult.Success.ToString());
  1015. else
  1016. fdcServer.TerminateFuellingSend(workstationID, applicationSender, requestId, (int)ErrorCode.ERRCD_INOP, OverallResult.Failure.ToString());
  1017. }
  1018. catch (Exception exx)
  1019. {
  1020. fdcLogger.LogError("OnTerminateFuellingReq exceptioned: " + exx);
  1021. fdcServer.TerminateFuellingSend(workstationID, applicationSender, requestId, (int)ErrorCode.ERRCD_NOTPOSSIBLE, OverallResult.Failure.ToString());
  1022. }
  1023. };
  1024. fdcServer.OnGetAvailableFuelSaleTrxsReq += (string workstationID, string applicationSender, int requestId, int deviceId) =>
  1025. {
  1026. // for limit data size, only show latest N days' data.
  1027. int maxReturnDays = 365;
  1028. // for further limit data size, only show latest N count of data.
  1029. int maxReturnDataCount = 1000;
  1030. lock (this.syncObject)
  1031. {
  1032. try
  1033. {
  1034. fdcLogger.LogDebug("OnGetAvailableFuelSaleTrxsReq (wid: " + workstationID + ", appSender: " + applicationSender + ", requestId: " + requestId + ", deviceId: " + deviceId);
  1035. var dueDate = DateTime.Now.Subtract(new TimeSpan(maxReturnDays, 0, 0, 0));
  1036. int totalReturnTrxCount = 0;
  1037. SqliteDbContext dbContext = new SqliteDbContext();
  1038. if (deviceId == -1)
  1039. {
  1040. var all = dbContext.PumpTransactionModels.Where(t => t.State != FuelSaleTransactionState.Paid
  1041. && t.State != FuelSaleTransactionState.Cleared
  1042. && t.SaleStartTime >= dueDate)
  1043. .Select(t => new { t.PumpId, t.TransactionSeqNumberFromPhysicalPump, t.State, t.SaleStartTime, t.ReleaseToken })
  1044. .OrderByDescending(r => r.SaleStartTime).Take(maxReturnDataCount);
  1045. foreach (var unpaidTrx in all)
  1046. {
  1047. // only show the trx in active pump list.
  1048. if (!fdcPumpControllers.Select(p => p.PumpId).Any(p => p == unpaidTrx.PumpId))
  1049. continue;
  1050. totalReturnTrxCount++;
  1051. fdcServer.GetAvailableFuelSaleTrxsAdd(workstationID, applicationSender, requestId,
  1052. unpaidTrx.PumpId,
  1053. int.Parse(unpaidTrx.TransactionSeqNumberFromPhysicalPump),
  1054. unpaidTrx.ReleaseToken.ToString(),
  1055. (int)unpaidTrx.State);
  1056. }
  1057. }
  1058. else
  1059. {
  1060. var all = dbContext.PumpTransactionModels.Where(t => t.PumpId == deviceId
  1061. && t.State != FuelSaleTransactionState.Paid
  1062. && t.State != FuelSaleTransactionState.Cleared
  1063. && t.SaleStartTime >= dueDate)
  1064. .Select(t => new { t.PumpId, t.TransactionSeqNumberFromPhysicalPump, t.State, t.SaleStartTime, t.ReleaseToken })
  1065. .OrderByDescending(r => r.SaleStartTime).Take(maxReturnDataCount);
  1066. foreach (var unpaidTrx in all)
  1067. {
  1068. // only show the trx in active pump list.
  1069. if (!fdcPumpControllers.Select(p => p.PumpId).Any(p => p == unpaidTrx.PumpId))
  1070. continue;
  1071. totalReturnTrxCount++;
  1072. fdcServer.GetAvailableFuelSaleTrxsAdd(workstationID, applicationSender, requestId,
  1073. unpaidTrx.PumpId,
  1074. int.Parse(unpaidTrx.TransactionSeqNumberFromPhysicalPump),
  1075. unpaidTrx.ReleaseToken.ToString(),
  1076. (int)unpaidTrx.State);
  1077. }
  1078. }
  1079. fdcServer.GetAvailableFuelSaleTrxsSend(workstationID, applicationSender, requestId,
  1080. (int)ErrorCode.ERRCD_OK, (totalReturnTrxCount > 0 ? OverallResult.Success.ToString() : OverallResult.NoData.ToString()));
  1081. fdcLogger.LogDebug(" OnGetAvailableFuelSaleTrxsReq done (total count: " + totalReturnTrxCount + ")");
  1082. }
  1083. catch (Exception exxx)
  1084. {
  1085. fdcLogger.LogError("OnGetAvailableFuelSaleTrxsReq exceptioned: " + exxx.ToString());
  1086. fdcServer.GetAvailableFuelSaleTrxsSend(workstationID, applicationSender, requestId,
  1087. (int)ErrorCode.ERRCD_BADCONF, OverallResult.Failure.ToString());
  1088. }
  1089. }
  1090. };
  1091. fdcServer.OnGetFuelSaleTrxDetailsReq += (string workstationID, string applicationSender, int requestId, int deviceId, int transactionNo, string releaseToken) =>
  1092. {
  1093. lock (this.syncObject)
  1094. {
  1095. try
  1096. {
  1097. /* Fdc client may send wildchar `*` in transactionNo which will be interpreted as -1 here, that means query all trx */
  1098. fdcLogger.LogDebug("OnGetFuelSaleTrxDetailsReq (wid: " + workstationID
  1099. + ", appSender: " + applicationSender + ", requestId: " + requestId
  1100. + ", deviceId: " + deviceId + ", transactionNo: " + transactionNo + ", releaseToken: " + releaseToken);
  1101. int databaseId = int.Parse(releaseToken);
  1102. SqliteDbContext dbContext = new SqliteDbContext();
  1103. List<Edge.Core.Database.Models.FuelSaleTransaction> target;
  1104. if (transactionNo == -1)
  1105. target = dbContext.PumpTransactionModels.Where(t => t.PumpId == deviceId).ToList();
  1106. else
  1107. target = dbContext.PumpTransactionModels.Where(t =>
  1108. t.ReleaseToken == databaseId
  1109. && t.PumpId == deviceId
  1110. && t.TransactionSeqNumberFromPhysicalPump == transactionNo.ToString()).ToList();
  1111. if (target.Any())
  1112. {
  1113. var targetController = fdcPumpControllers
  1114. .First(c => c.PumpId == deviceId) as IFdcPumpController;
  1115. foreach (var trx in target)
  1116. {
  1117. var reservedBy = string.IsNullOrEmpty(trx.LockedByFdcClientId) ?
  1118. -1 : int.Parse(trx.LockedByFdcClientId);
  1119. fdcServer.GetFuelSaleTrxDetailsAdd(workstationID, applicationSender, requestId,
  1120. deviceId,
  1121. trx.Volumn / Math.Pow(10, targetController.VolumeDecimalDigits),
  1122. trx.Amount / Math.Pow(10, targetController.AmountDecimalDigits),
  1123. trx.UnitPrice / Math.Pow(10, targetController.PriceDecimalDigits),
  1124. trx.LogicalNozzleId,
  1125. int.Parse(trx.ProductBarcode),
  1126. "", "", 1,
  1127. int.Parse(trx.TransactionSeqNumberFromPhysicalPump), releaseToken,
  1128. (int)trx.State,
  1129. trx.SaleStartTime?.ToString("yyyy-MM-dd HH:mm:ss"),
  1130. trx.SaleEndTime?.ToString("yyyy-MM-dd HH:mm:ss"),
  1131. trx.LockedByFdcClientId,
  1132. "9999",
  1133. reservedBy, 1);
  1134. }
  1135. fdcServer.GetFuelSaleTrxDetailsSend(workstationID, applicationSender, requestId,
  1136. (int)ErrorCode.ERRCD_OK, OverallResult.Success.ToString());
  1137. }
  1138. else
  1139. {
  1140. fdcLogger.LogInformation(" Trx with releaseToken: " + releaseToken + " is not found");
  1141. fdcServer.GetFuelSaleTrxDetailsSend(workstationID, applicationSender, requestId,
  1142. (int)ErrorCode.ERRCD_OK, OverallResult.NoData.ToString());
  1143. }
  1144. fdcLogger.LogDebug(" OnGetFuelSaleTrxDetailsReq done");
  1145. }
  1146. catch (Exception exxx)
  1147. {
  1148. fdcLogger.LogError("OnGetFuelSaleTrxDetailsReq exceptioned: " + exxx.ToString());
  1149. fdcServer.GetFuelSaleTrxDetailsSend(workstationID, applicationSender, requestId,
  1150. (int)ErrorCode.ERRCD_BADVAL, OverallResult.Failure.ToString());
  1151. }
  1152. }
  1153. };
  1154. fdcServer.OnClearFuelSaleTrxReq += async (string workstationID, string applicationSender, int requestId, int deviceId, int transactionNo, string releaseToken) =>
  1155. {
  1156. try
  1157. {
  1158. fdcLogger.LogDebug("OnClearFuelSaleTrxReq (wid: " + workstationID + ", appSender: " + applicationSender
  1159. + ", requestId: " + requestId + ", deviceId: " + deviceId
  1160. + ", transactionNo: " + transactionNo + ", releaseToken: " + releaseToken + ")");
  1161. var target = await this.ClearFuelSaleTrxAndNotifyAllFdcClientsAsync(deviceId, transactionNo.ToString(), int.Parse(releaseToken), workstationID, applicationSender, requestId);
  1162. if (target == null)
  1163. fdcLogger.LogError("OnClearFuelSaleTrxReq failed(wid: " + workstationID + ", appSender: " + applicationSender
  1164. + ", requestId: " + requestId + ", deviceId: " + deviceId
  1165. + ", transactionNo: " + transactionNo + ", releaseToken: " + releaseToken + ")");
  1166. }
  1167. catch (Exception exxx)
  1168. {
  1169. fdcLogger.LogError("OnClearFuelSaleTrxReq exceptioned: " + exxx.ToString());
  1170. try
  1171. {
  1172. fdcServer.ClearFuelSaleTrx(workstationID, applicationSender, requestId, deviceId, transactionNo, releaseToken,
  1173. (int)ErrorCode.ERRCD_BADVAL, 1, OverallResult.Failure.ToString());
  1174. }
  1175. catch (Exception eeee)
  1176. {
  1177. fdcLogger.LogError("Fdc server is trying to respond a failed ClearFuelSaleTrx response to a fdcclient that issue current ClearFuelSaleTrx request, but still failed with reason: " + eeee);
  1178. }
  1179. }
  1180. };
  1181. // FdcClient send ServiceRequestChangeFuelPrice will be routed into here
  1182. fdcServer.OnChangeFuelPriceInStringReq += (string workstationID,
  1183. string applicationSender, int requestId, string formattedValues) =>
  1184. {
  1185. try
  1186. {
  1187. fdcLogger.LogInformation("OnChangeFuelPriceInStringReq (requestId:" +
  1188. requestId + ") with data-> (barcode; PriceNew; ModeNo; PriceOld; EffectiveDatetime;!): " + formattedValues);
  1189. //formattedValues is: gradeId(barCode);PriceNew;ModeNo;EffectiveDatetime! e.g.: 5;1980;1;!
  1190. //this function should return: gradeId(barCode);PriceNew;ModeNo;PriceOld;EffectiveDatetime;! e.g.: 2;1998;1;1981;!
  1191. var targetProductBarcode = int.Parse(formattedValues.Split(';')[0]);
  1192. // raw means the price is send from POS, and typically it contains decimal points,
  1193. // need re-caculate with money digits since pump does not recognize decimal points.
  1194. var rawNewPriceWithHumanReadableFormat = double.Parse(formattedValues.Split(';')[1]);
  1195. var succeedNozzles = this.ChangeFuelPriceAsync(targetProductBarcode, rawNewPriceWithHumanReadableFormat).Result;
  1196. fdcLogger.LogInformation(" OnChangeFuelPriceInStringReq (requestId:" + requestId + ") is done");
  1197. // even only one succeed, return a success. need refine?
  1198. if (succeedNozzles != null && succeedNozzles.Any())
  1199. {
  1200. // notify all tcp FdcClients that price changed.
  1201. fdcServer.FuelPriceChange(targetProductBarcode, 1,
  1202. rawNewPriceWithHumanReadableFormat,
  1203. 0,
  1204. // always set older time since the new price is already activated on pump.
  1205. DateTime.Now.Subtract(new TimeSpan(0, 5, 0)).ToString("yyyy-MM-dd HH:mm:ss"));
  1206. return targetProductBarcode.ToString() + ";" + rawNewPriceWithHumanReadableFormat.ToString() + ";1;" + 0 + ";!";
  1207. }
  1208. else return null;
  1209. }
  1210. catch (Exception exxx)
  1211. {
  1212. fdcLogger.LogError("OnChangeFuelPriceInStringReq exceptioned: " + exxx);
  1213. return null;
  1214. }
  1215. };
  1216. //fdcServer.OnChangeFuelPriceAddReq += (string workstationID, string applicationSender,
  1217. // int requestId, int product, double price, int mode, string effectiveDateTime) =>
  1218. // {
  1219. // };
  1220. //fdcServer.OnChangeFuelPriceEndReq += (string workstationID, string applicationSender, int requestId) =>
  1221. // {
  1222. // fdcServer.ChangeFuelPriceEndReq(workstationID, applicationSender, requestId);
  1223. // return "";
  1224. // };
  1225. fdcServer.OnGetCurrentFuellingStatusReq += (string workstationID, string applicationSender,
  1226. int requestId, int deviceId) =>
  1227. {
  1228. /* do nothing for now, the fuelling status will notify by unsolicited event. */
  1229. var targetController = fdcPumpControllers.First(c => c.PumpId == deviceId) as IFdcPumpController;
  1230. //if (targetController.QueryStatus() == LogicalDeviceState.FDC_FUELLING)
  1231. //{
  1232. // fdcServer.GetCurrentFuellingStatusAdd(workstationID, applicationSender, requestId, deviceId)
  1233. //}
  1234. };
  1235. fdcServer.OnGetFuelPointTotalsReq += async (string workstationID, string applicationSender, int requestId, int deviceId, int nozzleId) =>
  1236. {
  1237. try
  1238. {
  1239. fdcLogger.LogDebug("OnGetFuelPointTotalsReq (wid: " + workstationID + ", appSender: " + applicationSender + ", requestId: " + requestId + ", deviceId: " + deviceId
  1240. + ", nozzleId: " + nozzleId);
  1241. if (deviceId == -1)
  1242. {
  1243. /* query all pumps and all its nozzles */
  1244. var everReadSuccessfully = false;
  1245. foreach (var pumpController in fdcPumpControllers)
  1246. {
  1247. var pumpState = await pumpController.QueryStatusAsync();
  1248. if (pumpState != LogicalDeviceState.FDC_READY)
  1249. {
  1250. fdcLogger.LogDebug($" OnGetFuelPointTotalsReq, pump: {pumpController.PumpId} is in state: {pumpState}, will skip to query its totalizer.");
  1251. continue;
  1252. }
  1253. foreach (var nozzle in pumpController.Nozzles)
  1254. {
  1255. var result = await this.GetFuelPointTotalsAsync(pumpController.PumpId, (byte)nozzle.LogicalId);
  1256. everReadSuccessfully = true;
  1257. fdcLogger.LogInformation(" OnGetFuelPointTotalsReq for pump: " + pumpController.PumpId
  1258. + ", nozzle: " + nozzle.LogicalId + " result(with decimal points) is: " + result.Item1 + " <-> " + result.Item2);
  1259. fdcServer.GetFuelPointTotalsAdd(workstationID, applicationSender, requestId, pumpController.PumpId, nozzle.LogicalId,
  1260. nozzleExtraInfos.FirstOrDefault(n => n.PumpId == pumpController.PumpId && n.NozzleLogicalId == nozzle.LogicalId).ProductBarcode,
  1261. result.Item2,
  1262. result.Item1,
  1263. (pumpController.Nozzles.FirstOrDefault(n => n.LogicalId == nozzleId)?.RealPriceOnPhysicalPump ?? 0) / Math.Pow(10, pumpController.PriceDecimalDigits));
  1264. }
  1265. }
  1266. if (everReadSuccessfully)
  1267. fdcServer.GetFuelPointTotalsSend(workstationID, applicationSender, requestId, (int)ErrorCode.ERRCD_OK, OverallResult.Success.ToString());
  1268. else
  1269. fdcServer.GetFuelPointTotalsSend(workstationID, applicationSender, requestId, (int)ErrorCode.ERRCD_NOTALLOWED, OverallResult.DeviceUnavailable.ToString());
  1270. }
  1271. else if (nozzleId == -1)
  1272. {
  1273. /* query a specified pump and all its nozzles */
  1274. var pumpController = fdcPumpControllers
  1275. .First(c => c.PumpId == deviceId) as IFdcPumpController;
  1276. var pumpState = await pumpController.QueryStatusAsync();
  1277. if (pumpState != LogicalDeviceState.FDC_READY)
  1278. {
  1279. fdcLogger.LogDebug($" OnGetFuelPointTotalsReq, pump: {pumpController.PumpId} is in state: {pumpState}, will skip to query its totalizer.");
  1280. fdcServer.GetFuelPointTotalsSend(workstationID, applicationSender, requestId, (int)ErrorCode.ERRCD_NOTALLOWED, OverallResult.DeviceUnavailable.ToString());
  1281. }
  1282. else
  1283. {
  1284. foreach (var nozzle in pumpController.Nozzles)
  1285. {
  1286. var result = await this.GetFuelPointTotalsAsync(pumpController.PumpId, (byte)nozzle.LogicalId);
  1287. fdcLogger.LogInformation(" OnGetFuelPointTotalsReq for pump: " + pumpController.PumpId + ", nozzle: " + nozzle.LogicalId + " result(with decimal points) is: " + result.Item1 + " <-> " + result.Item2);
  1288. fdcServer.GetFuelPointTotalsAdd(workstationID, applicationSender, requestId, pumpController.PumpId, nozzle.LogicalId,
  1289. nozzleExtraInfos.FirstOrDefault(n => n.PumpId == pumpController.PumpId && n.NozzleLogicalId == nozzle.LogicalId).ProductBarcode,
  1290. result.Item2,
  1291. result.Item1,
  1292. (pumpController.Nozzles.FirstOrDefault(n => n.LogicalId == nozzleId)?.RealPriceOnPhysicalPump ?? 0) / Math.Pow(10, pumpController.PriceDecimalDigits));
  1293. }
  1294. fdcServer.GetFuelPointTotalsSend(workstationID, applicationSender, requestId, (int)ErrorCode.ERRCD_OK, OverallResult.Success.ToString());
  1295. }
  1296. }
  1297. else
  1298. {
  1299. /* query a specified pump and a specified nozzle */
  1300. var targetController = fdcPumpControllers
  1301. .First(c => c.PumpId == deviceId) as IFdcPumpController;
  1302. var pumpState = await targetController.QueryStatusAsync();
  1303. if (pumpState != LogicalDeviceState.FDC_READY)
  1304. {
  1305. fdcLogger.LogDebug($" OnGetFuelPointTotalsReq, pump: {targetController.PumpId} is in state: {pumpState}, will skip to query its totalizer.");
  1306. fdcServer.GetFuelPointTotalsSend(workstationID, applicationSender, requestId, (int)ErrorCode.ERRCD_NOTALLOWED, OverallResult.DeviceUnavailable.ToString());
  1307. }
  1308. else
  1309. {
  1310. var result = await this.GetFuelPointTotalsAsync(targetController.PumpId, (byte)nozzleId);
  1311. fdcLogger.LogInformation(" OnGetFuelPointTotalsReq for pump: " + targetController.PumpId + ", nozzle: " + nozzleId + " result(with decimal points) is: " + result.Item1 + " <-> " + result.Item2);
  1312. fdcServer.GetFuelPointTotalsAdd(workstationID, applicationSender, requestId, deviceId, nozzleId,
  1313. nozzleExtraInfos.FirstOrDefault(n => n.PumpId == targetController.PumpId && n.NozzleLogicalId == nozzleId).ProductBarcode,
  1314. result.Item2,
  1315. result.Item1,
  1316. (targetController.Nozzles.FirstOrDefault(n => n.LogicalId == nozzleId)?.RealPriceOnPhysicalPump ?? 0) / Math.Pow(10, targetController.PriceDecimalDigits));
  1317. fdcServer.GetFuelPointTotalsSend(workstationID, applicationSender, requestId, (int)ErrorCode.ERRCD_OK, OverallResult.Success.ToString());
  1318. }
  1319. }
  1320. fdcLogger.LogDebug(" OnGetFuelPointTotals done");
  1321. }
  1322. catch (Exception exxx)
  1323. {
  1324. fdcLogger.LogError("OnGetFuelPointTotalsReq exceptioned: " + exxx.ToString());
  1325. fdcServer.GetFuelPointTotalsSend(workstationID, applicationSender, requestId,
  1326. (int)ErrorCode.ERRCD_BADCONF, OverallResult.Failure.ToString());
  1327. }
  1328. };
  1329. #region setup Fdc unsolicited event
  1330. foreach (var fdcPumpController in this.fdcPumpControllers)
  1331. {
  1332. fdcPumpController.OnStateChange += async (s, stateChangeArg) =>
  1333. {
  1334. var pump = s as IFdcPumpController;
  1335. try
  1336. {
  1337. fdcLogger.LogDebug("Pump " + pump.PumpId
  1338. + " StateChanged to: " + stateChangeArg.NewPumpState.ToString() + ", nozzles states are(LogicalId-State): "
  1339. + (pump.Nozzles != null ?
  1340. (pump.Nozzles.Any() ?
  1341. pump.Nozzles.Select(n =>
  1342. n.LogicalId.ToString() + "-" + (n.LogicalState?.ToString() ?? ""))
  1343. .Aggregate((n, acc) => n + ", " + acc) : "") : "")
  1344. + ", StateChangedNozzles are: "
  1345. + (stateChangeArg.StateChangedNozzles != null ?
  1346. (stateChangeArg.StateChangedNozzles.Any() ?
  1347. stateChangeArg.StateChangedNozzles.Select(n => n.LogicalId.ToString())
  1348. .Aggregate((n, acc) => n + ", " + acc) : "") : ""));
  1349. this.OnStateChange?.Invoke(s, stateChangeArg);
  1350. var universalApiHub = this.services.GetRequiredService<UniversalApiHub>();
  1351. await universalApiHub.FireEvent(this, "OnFdcControllerStateChange",
  1352. new
  1353. {
  1354. pump.PumpId,
  1355. logicalId = (stateChangeArg.StateChangedNozzles != null && stateChangeArg.StateChangedNozzles.Any()) ?
  1356. stateChangeArg.StateChangedNozzles.FirstOrDefault().LogicalId.ToString() : "",
  1357. logicalState = stateChangeArg.NewPumpState
  1358. });
  1359. fdcLogger.LogTrace("Pump " + pump.PumpId + " StateChanged event fired and back");
  1360. // this is used for send to fdc client.
  1361. byte nozzleUpOrDownBitMap = 0;
  1362. if (stateChangeArg.StateChangedNozzles != null)
  1363. foreach (var upNozzle in stateChangeArg.StateChangedNozzles)
  1364. {
  1365. nozzleUpOrDownBitMap = SetBit(nozzleUpOrDownBitMap, upNozzle.LogicalId - 1, upNozzle.LogicalId, 1);
  1366. }
  1367. byte nozzleLockedOrUnlockedBitMap = 0;
  1368. foreach (var nozzle in pump.Nozzles)
  1369. {
  1370. if (nozzle.LogicalState.HasValue && nozzle.LogicalState == LogicalDeviceState.FDC_LOCKED)
  1371. nozzleLockedOrUnlockedBitMap = SetBit(nozzleLockedOrUnlockedBitMap, nozzle.LogicalId - 1, nozzle.LogicalId, 1);
  1372. else
  1373. nozzleLockedOrUnlockedBitMap = SetBit(nozzleLockedOrUnlockedBitMap, nozzle.LogicalId - 1, nozzle.LogicalId, 0);
  1374. }
  1375. fdcServer.DeviceStateChange(Wayne.FDCPOSLibrary.DeviceType.DT_FuellingPoint, pump.PumpId,
  1376. (int)(stateChangeArg.NewPumpState), (int)LogicalDeviceState.FDC_UNDEFINED, "", "", "",
  1377. pump.Nozzles.Count(), nozzleUpOrDownBitMap, nozzleLockedOrUnlockedBitMap, 0);
  1378. fdcLogger.LogTrace($"Pump {pump.PumpId } Fdc DeviceStateChange event fired and back");
  1379. if (stateChangeArg.NewPumpState == LogicalDeviceState.FDC_CALLING)
  1380. {
  1381. if (this.config_AutoAuthCallingPumps)
  1382. {
  1383. if (this.config_MaxStackUnpaidTrxPerPump > 0)
  1384. {
  1385. SqliteDbContext dbContext = new SqliteDbContext();
  1386. var unpaidTrxCount = dbContext.PumpTransactionModels.Count(t => t.PumpId == pump.PumpId && t.State == FuelSaleTransactionState.Payable);
  1387. if (unpaidTrxCount >= this.config_MaxStackUnpaidTrxPerPump)
  1388. {
  1389. fdcLogger.LogInformation(" Auto authorizing is not permit since pump " + pump.PumpId + " has: " + unpaidTrxCount + " unpaid trx");
  1390. return;
  1391. }
  1392. }
  1393. fdcLogger.LogDebug("Auto authorizing Pump: " + pump.PumpId);
  1394. int autoAuthDefaultAmount = 9999;
  1395. var result = await pump.AuthorizeWithAmountAsync((int)(autoAuthDefaultAmount * Math.Pow(10, pump.AmountDecimalDigits)), 1);
  1396. if (!result)
  1397. fdcLogger.LogError("Auto auth Pump: " + pump.PumpId + " FAILED!");
  1398. }
  1399. }
  1400. }
  1401. catch (Exception exxx)
  1402. {
  1403. fdcLogger.LogError($"PumpId: {pump.PumpId}, fdcPumpController.OnStateChange exceptioned: {exxx}");
  1404. }
  1405. };
  1406. fdcPumpController.OnCurrentFuellingStatusChange += async (s, a) =>
  1407. {
  1408. var pump = s as IFdcPumpController;
  1409. try
  1410. {
  1411. fdcLogger.LogDebug($"Pump {pump.PumpId }, Nozzle: {a?.Transaction?.Nozzle?.LogicalId.ToString() ?? ""}, OnCurrentFuellingStatusChange");
  1412. var product = nozzleExtraInfos.FirstOrDefault(c => c.PumpId == pump.PumpId && c.NozzleLogicalId == a.Transaction.Nozzle.LogicalId);
  1413. if (product != null)
  1414. a.Transaction.Barcode = product.ProductBarcode;
  1415. else
  1416. a.Transaction.Barcode = 1;
  1417. if (a.Transaction.Finished)
  1418. {
  1419. fdcLogger.LogInformation("Pump " + pump.PumpId + ", transaction is finished, vol: " + a.Transaction.Volumn + ", amount: " + a.Transaction.Amount
  1420. + ", price: " + a.Transaction.Price + ", nozzleNo: " + a.Transaction.Nozzle.LogicalId
  1421. + ", volumeTotalizer: " + (a.Transaction.VolumeTotalizer ?? -1)
  1422. + ", amountTotalizer: " + (a.Transaction.AmountTotalizer ?? -1)
  1423. + ", seqNumber: " + a.Transaction.SequenceNumberGeneratedOnPhysicalPump
  1424. + ", productBarcode: " + product?.ProductBarcode
  1425. + ", productName: " + (product?.ProductName ?? ""));
  1426. //enum SaleTrxStatus
  1427. //{
  1428. // SALE_TRX_UNDEFINED = 0,
  1429. // SALE_TRX_PAYABLE = 1,
  1430. // SALE_TRX_LOCKED = 2,
  1431. // SALE_TRX_PAID = 3,
  1432. // SALE_TRX_CLEARED = 4,
  1433. //};
  1434. // will do a duplication search in a short time range(by days) since physical pump will reuse seq id.
  1435. var duplicationDectectTimeRange = 3;
  1436. var range = DateTime.Now.Subtract(new TimeSpan(duplicationDectectTimeRange, 0, 0, 0));
  1437. SqliteDbContext dbContext = new SqliteDbContext();
  1438. var existed = dbContext.PumpTransactionModels.Where(f =>
  1439. f.PumpId == pump.PumpId
  1440. && f.TransactionSeqNumberFromPhysicalPump == a.Transaction.SequenceNumberGeneratedOnPhysicalPump.ToString()
  1441. && f.UnitPrice == a.Transaction.Price
  1442. && f.Amount == a.Transaction.Amount
  1443. && f.Volumn == a.Transaction.Volumn
  1444. && f.SaleEndTime > range).ToList();
  1445. if (existed.Any())
  1446. {
  1447. fdcLogger.LogWarning("A new trx duplicated with an existed trx in db which done in recent "
  1448. + duplicationDectectTimeRange + " days, it was with " +
  1449. "releaseToken:" + existed.First().ReleaseToken + ", pumpId: " + pump.PumpId + " and seqNo: "
  1450. + a.Transaction.SequenceNumberGeneratedOnPhysicalPump.ToString()
  1451. + ", will do nothing and NOT notify any POS, while the existed one in database detail is-> "
  1452. + "Vol: " + existed.First().Volumn
  1453. + ", Amount: " + existed.First().Amount
  1454. + ", State: " + existed.First().State.ToString()
  1455. + ", SaleStartTime: " + (existed.First().SaleStartTime?.ToString("yyyy-MM-dd HH:mm:ss") ?? "")
  1456. + ", EndStartTime: " + (existed.First().SaleEndTime?.ToString("yyyy-MM-dd HH:mm:ss") ?? "")
  1457. + ", ProductBarcode: " + existed.First().ProductBarcode.ToString()
  1458. + "");
  1459. }
  1460. else
  1461. {
  1462. var trx = new Edge.Core.Database.Models.FuelSaleTransaction()
  1463. {
  1464. TransactionSeqNumberFromPhysicalPump = a.Transaction.SequenceNumberGeneratedOnPhysicalPump.ToString(),
  1465. PumpId = pump.PumpId,
  1466. LogicalNozzleId = a.Transaction.Nozzle.LogicalId,
  1467. Amount = a.Transaction.Amount,
  1468. Volumn = a.Transaction.Volumn,
  1469. UnitPrice = a.Transaction.Price,
  1470. VolumeTotalizer = a.Transaction.VolumeTotalizer ?? -1,
  1471. AmountTotalizer = a.Transaction.AmountTotalizer ?? -1,
  1472. ProductBarcode = product?.ProductBarcode.ToString() ?? "9999",
  1473. //ProductName = "refer cloud",
  1474. State = FuelSaleTransactionState.Payable,
  1475. LockedByFdcClientId = "",
  1476. // hard code for now, need do it in PumpHandler
  1477. SaleStartTime = a.Transaction.SaleStartTime ?? ((a.Transaction.SaleEndTime ?? DateTime.Now).Subtract(new TimeSpan(0, 3, 0))),
  1478. SaleEndTime = a.Transaction.SaleEndTime ?? DateTime.Now,
  1479. };
  1480. dbContext.PumpTransactionModels.Add(trx);
  1481. dbContext.SaveChanges();
  1482. fdcLogger.LogDebug(" ######transaction is done saving to db with ReleaseToken(Id): " + trx.ReleaseToken);
  1483. var safe = this.OnFdcFuelSaleTransactinStateChange;
  1484. safe?.Invoke(this, new FdcFuelSaleTransactinStateChangeEventArg(trx, FuelSaleTransactionState.Payable));
  1485. var universalApiHub = this.services.GetRequiredService<UniversalApiHub>();
  1486. await universalApiHub.FireEvent(this, "OnFdcFuelSaleTransactinStateChange", new FdcFuelSaleTransactinStateChangeEventArg(trx, FuelSaleTransactionState.Payable));
  1487. var startingTime = DateTime.Now;
  1488. fdcServer.FuelSaleTrx(pump.PumpId,
  1489. trx.Volumn / Math.Pow(10, pump.VolumeDecimalDigits),
  1490. trx.Amount / Math.Pow(10, pump.AmountDecimalDigits),
  1491. trx.UnitPrice / Math.Pow(10, pump.PriceDecimalDigits),
  1492. trx.LogicalNozzleId,
  1493. product?.ProductBarcode ?? 0,
  1494. product?.ProductName ?? ("refer cloud for name of " + (product?.ProductBarcode ?? -1)),
  1495. "", 1,
  1496. int.Parse(trx.TransactionSeqNumberFromPhysicalPump),
  1497. (int)trx.State,
  1498. 0, trx.ReleaseToken.ToString(),
  1499. trx.SaleStartTime?.ToString("yyyy-MM-dd HH:mm:ss"),
  1500. trx.SaleEndTime?.ToString("yyyy-MM-dd HH:mm:ss"),
  1501. "", "", 0, 0);
  1502. fdcLogger.LogDebug($" ######transaction(releaseToken: {trx.ReleaseToken}) is done broadcasting to POSes, used: {DateTime.Now.Subtract(startingTime).TotalMilliseconds}");
  1503. this.OnCurrentFuellingStatusChange?.Invoke(s,
  1504. new FdcServerTransactionDoneEventArg(a.Transaction)
  1505. { ReleaseToken = trx.ReleaseToken });
  1506. fdcLogger.LogTrace("Pump " + pump.PumpId + " OnCurrentFuellingStatusChange event fired and back");
  1507. if (a.Transaction.VolumeTotalizer == null)
  1508. {
  1509. var result = await GetFuelPointTotalsAsync(pump.PumpId, a.Transaction.Nozzle.LogicalId);
  1510. a.Transaction.AmountTotalizer = (int?)(result.Item1 * Math.Pow(10, pump.AmountDecimalDigits));
  1511. a.Transaction.VolumeTotalizer = (int?)(result.Item2 * Math.Pow(10, pump.VolumeDecimalDigits));
  1512. }
  1513. var targetNozzle = pump.Nozzles.First(n => n.LogicalId == a.Transaction.Nozzle.LogicalId);
  1514. if (targetNozzle != null) targetNozzle.VolumeTotalizer = a.Transaction.VolumeTotalizer;
  1515. await universalApiHub.FireEvent(this, "OnCurrentFuellingStatusChange", new FdcServerTransactionDoneEventArg(a.Transaction) { FuelingEndTime = trx.SaleEndTime });
  1516. }
  1517. }
  1518. else
  1519. {
  1520. fdcLogger.LogDebug(" transaction is ongoing, vol: " + a.Transaction.Volumn + ", amount: " + a.Transaction.Amount
  1521. + ", price: " + a.Transaction.Price + ", nozzleNo: " + a.Transaction.Nozzle.LogicalId
  1522. + ", seqNumber: " + a.Transaction.SequenceNumberGeneratedOnPhysicalPump ?? ""
  1523. + ", productBarcode: " + product.ProductBarcode
  1524. + ", productName: " + (product.ProductName ?? ""));
  1525. fdcServer.CurrentFuellingStatus(pump.PumpId,
  1526. a.Transaction.Volumn / Math.Pow(10, pump.VolumeDecimalDigits),
  1527. a.Transaction.Amount / Math.Pow(10, pump.AmountDecimalDigits),
  1528. a.Transaction.Price / Math.Pow(10, pump.PriceDecimalDigits),
  1529. a.Transaction.Nozzle.LogicalId,
  1530. a.Transaction.SequenceNumberGeneratedOnPhysicalPump, "9999", 1, a.Transaction.Nozzle.LogicalId);
  1531. this.OnCurrentFuellingStatusChange?.Invoke(s, new FdcServerTransactionDoneEventArg(a.Transaction));
  1532. fdcLogger.LogTrace("Pump " + pump.PumpId + " OnCurrentFuellingStatusChange event fired and back");
  1533. var universalApiHub = this.services.GetRequiredService<UniversalApiHub>();
  1534. await universalApiHub.FireEvent(this, "OnCurrentFuellingStatusChange", new FdcServerTransactionDoneEventArg(a.Transaction));
  1535. }
  1536. }
  1537. catch (Exception exxx)
  1538. {
  1539. fdcLogger.LogError($"PumpId: { pump?.PumpId ?? -1}, fdcPumpController.OnCurrentFuellingStatusChange exceptioned: " + exxx);
  1540. }
  1541. };
  1542. }
  1543. #endregion
  1544. #region handle BroadcastGenericTypelessMessage
  1545. if (this.fdcCommunicableControllers != null)
  1546. {
  1547. this.fdcCommunicableControllers.ToList().ForEach(c =>
  1548. {
  1549. c.BroadcastMessageViaFdc += (msg) =>
  1550. {
  1551. fdcServer.SendGenericTypelessMessageToFdcClient(null, null, msg);
  1552. return true;
  1553. };
  1554. });
  1555. this.fdcCommunicableControllers.ToList().ForEach(c =>
  1556. {
  1557. c.SendMessageViaFdc += (workstationID, applicationSender, msg) =>
  1558. {
  1559. fdcServer.SendGenericTypelessMessageToFdcClient(workstationID, applicationSender, msg);
  1560. return true;
  1561. };
  1562. });
  1563. }
  1564. fdcServer.OnGetGenericTypelessMessageReq += (string workstationID, string applicationSender, int requestId, string message) =>
  1565. {
  1566. this.fdcCommunicableControllers.ToList().ForEach(c =>
  1567. {
  1568. if (c.OnMessageReceivedViaFdc != null)
  1569. {
  1570. var returnResult = c.OnMessageReceivedViaFdc(message);
  1571. fdcServer.GenericTypelessMessageSend(workstationID, applicationSender, requestId, returnResult.Item1, returnResult.Item2.ToString());
  1572. }
  1573. });
  1574. };
  1575. #endregion
  1576. fdcLogger.LogDebug("Start all FdcPumpController initing...(total: "
  1577. + (fdcPumpControllers?.Count() ?? -1)
  1578. + ", pump Ids are: " + ((fdcPumpControllers?.Any() ?? false) ?
  1579. (fdcPumpControllers.Select(c => c.PumpId.ToString()).Aggregate((acc, n) => acc + ", " + n) + ")") : ")"));
  1580. foreach (var fdcPumpController in this.fdcPumpControllers)
  1581. {
  1582. var onFdcServerInitParams = new Dictionary<string, object>();
  1583. try
  1584. {
  1585. #region LastPriceChange for each nozzles
  1586. // nozzle logical id:rawPrice
  1587. var innerParams0 = new Dictionary<byte, int>();
  1588. foreach (var nozzle in fdcPumpController.Nozzles)
  1589. {
  1590. var dbContext = new SqliteDbContext();
  1591. var lastPriceChange = dbContext.FuelPriceChanges
  1592. .Where(t => t.PumpId == fdcPumpController.PumpId
  1593. && t.LogicalNozzleId == nozzle.LogicalId)
  1594. .OrderByDescending(b => b.Id).FirstOrDefault();
  1595. if (lastPriceChange != null)
  1596. innerParams0.Add(nozzle.LogicalId, lastPriceChange.NewPriceWithoutDecimal);
  1597. }
  1598. onFdcServerInitParams.Add("LastPriceChange", innerParams0);
  1599. #endregion
  1600. #region LastFuelSaleTrx for each nozzles
  1601. // nozzle logical id:LastSaleTrx
  1602. var innerParams1 = new Dictionary<byte, FuelSaleTransaction>();
  1603. foreach (var nozzle in fdcPumpController.Nozzles)
  1604. {
  1605. var dbContext = new SqliteDbContext();
  1606. var lastPaidTrx = dbContext.PumpTransactionModels
  1607. .Where(t => t.PumpId == fdcPumpController.PumpId
  1608. && t.LogicalNozzleId == nozzle.LogicalId)
  1609. .OrderByDescending(b => b.SaleStartTime).FirstOrDefault();
  1610. if (lastPaidTrx != null)
  1611. innerParams1.Add(nozzle.LogicalId, lastPaidTrx);
  1612. }
  1613. onFdcServerInitParams.Add("LastFuelSaleTrx", innerParams1);
  1614. #endregion
  1615. }
  1616. catch (Exception exxx)
  1617. {
  1618. fdcLogger.LogError("Retrieve lastPriceChange for OnFdcServerInit from db for Pump with pumpid: "
  1619. + fdcPumpController.PumpId + " exceptioned: " + exxx.ToString()
  1620. + System.Environment.NewLine + "Will skip this pump and continue for next pump.");
  1621. continue;
  1622. }
  1623. try
  1624. {
  1625. fdcPumpController.OnFdcServerInit(onFdcServerInitParams);
  1626. }
  1627. catch (Exception exxx)
  1628. {
  1629. fdcLogger.LogError("FdcPumpController with pumpid: "
  1630. + fdcPumpController.PumpId + " exceptioned in OnFdcServerInit, detail: " + exxx.ToString()
  1631. + System.Environment.NewLine + "Will skip and continue for next FdcPumpController.");
  1632. continue;
  1633. }
  1634. }
  1635. fdcLogger.LogDebug("Done all FdcPumpController init, start FdcServer tcp listening...");
  1636. var _ = fdcServer.Start(this.config_ListeningPort, true, "WINCOR", 2, true, "NONE");
  1637. fdcLogger.LogDebug("Done FdcServer tcp listening...");
  1638. return Task.FromResult(_);
  1639. }
  1640. public Task<bool> Stop()
  1641. {
  1642. if (this.onConfiguratorConfigFileChangedEventHandler != null)
  1643. this.configurator.OnConfigFileChanged -= this.onConfiguratorConfigFileChangedEventHandler;
  1644. this.purgeDatabaseTimer?.Stop();
  1645. // set fdcPumpControllers to an empty list, as it needs to be stopped from firing any event to any substriber.
  1646. this.fdcPumpControllers = new List<IFdcPumpController>();
  1647. var stopFdcServerResult = fdcServer.Stop();
  1648. return Task.FromResult(stopFdcServerResult);
  1649. }
  1650. private static byte SetBit(byte target, int bitStartIndex, int bitEndIndex, int replacedValue)
  1651. {
  1652. if (bitStartIndex < 0 || bitEndIndex > 7 || bitEndIndex < bitStartIndex)
  1653. {
  1654. throw new ArgumentException("bitStartIndex or bitEndIndex value is not valid");
  1655. }
  1656. byte mask = 0;
  1657. for (int i = 0; i < bitEndIndex - bitStartIndex + 1; i++)
  1658. {
  1659. mask += (byte)Math.Pow(2, i);
  1660. }
  1661. if (replacedValue > mask)
  1662. {
  1663. throw new ArgumentOutOfRangeException("Replaced value: " + replacedValue + " cannot fit the bits range");
  1664. }
  1665. byte maskedValue = (byte)(target & (255 - (mask << bitStartIndex)));
  1666. return (byte)(maskedValue + (replacedValue << bitStartIndex));
  1667. }
  1668. public void Dispose()
  1669. {
  1670. }
  1671. #region pump control interface opened for local call, used in other fc app.
  1672. /// <summary>
  1673. /// fired once PumpController state changed.
  1674. /// </summary>
  1675. public event EventHandler<FdcPumpControllerOnStateChangeEventArg> OnStateChange;
  1676. /// <summary>
  1677. /// fired once the fuelling trx from PumpController state changed.
  1678. /// used to watch fuelling progress for a PumpController.
  1679. /// </summary>
  1680. public event EventHandler<FdcServerTransactionDoneEventArg> OnCurrentFuellingStatusChange;
  1681. /// <summary>
  1682. /// fired once the Fdc fuel sale trx state changed, like it's turn into Payable, Locked, Unlocked state etc.
  1683. /// used for local app processors to watch the trx state, the state change mostly triggered by Fdc client request.
  1684. /// </summary>
  1685. public event EventHandler<FdcFuelSaleTransactinStateChangeEventArg> OnFdcFuelSaleTransactinStateChange;
  1686. public IEnumerable<IFdcPumpController> FdcPumpControllers => this.fdcPumpControllers;
  1687. /// <summary>
  1688. /// maxTrxAmount has the high priority.
  1689. /// </summary>
  1690. /// <param name="pumpId">the target pump, which for authorizing</param>
  1691. /// <param name="maxTrxAmount">human readable number, with decimal point, like 5 RMB, should input 5. leave 0 if set with unlimited amount.</param>
  1692. /// <param name="maxTrxVolume">human readable number, with decimal point, like 6.5L, should input 6.5. leave 0 if set with unlimited vol</param>
  1693. /// <returns></returns>
  1694. public async Task<bool> AuthorizePumpAsync(int pumpId, double maxTrxAmount, double maxTrxVolume)
  1695. {
  1696. fdcLogger.LogDebug("Authorizing Pump: " + pumpId + ", maxTrxAmount: " + maxTrxAmount + ", maxTrxVolume: " + maxTrxVolume);
  1697. var targetController = fdcPumpControllers
  1698. .First(c => c.PumpId == pumpId) as IFdcPumpController;
  1699. if (this.config_MaxStackUnpaidTrxPerPump > 0)
  1700. {
  1701. SqliteDbContext dbContext = new SqliteDbContext();
  1702. var unpaidTrxCount = dbContext.PumpTransactionModels.Count(t => t.PumpId == targetController.PumpId
  1703. && t.State == FuelSaleTransactionState.Payable);
  1704. if (unpaidTrxCount >= this.config_MaxStackUnpaidTrxPerPump)
  1705. {
  1706. fdcLogger.LogInformation(" Authorizing from FdcClient is not permit since pump " + targetController.PumpId + " has: " + unpaidTrxCount + " unpaid trx");
  1707. //fdcServer.AuthoriseFuelPoint(workstationID, applicationSender, requestId, -99, releaseToken, deviceId,
  1708. // (int)LogicalDeviceState.FDC_READY, (int)ErrorCode.ERRCD_MAXSTACKLIMIT, OverallResult.Failure.ToString());
  1709. return false;
  1710. }
  1711. }
  1712. bool succeed = false;
  1713. if (maxTrxAmount == 0 && maxTrxVolume == 0)
  1714. // fuel with unlimited
  1715. succeed = await targetController.AuthorizeAsync(1);
  1716. else if (maxTrxAmount == 0 && maxTrxVolume != 0)
  1717. succeed = await targetController.AuthorizeWithVolumeAsync((int)(maxTrxVolume * Math.Pow(10, targetController.VolumeDecimalDigits)), 1);
  1718. else
  1719. succeed = await targetController.AuthorizeWithAmountAsync((int)(maxTrxAmount * Math.Pow(10, targetController.AmountDecimalDigits)), 1);
  1720. fdcLogger.LogDebug(" AuthorizePump: " + pumpId + " finished with: " + succeed.ToString());
  1721. return succeed;
  1722. }
  1723. /// <summary>
  1724. /// Clear an unpaid fuel sale trx from db and notify all tcp FdcClients.
  1725. /// This function is used for non-tcp-FdcClient caller, which are most likely the Fcc Apps,
  1726. /// so it's a local in-process call.
  1727. /// </summary>
  1728. /// <param name="pumpId">pump id of the target trx belongs</param>
  1729. /// <param name="transactionNo">trx Number of the target trx that is on clearing</param>
  1730. /// <param name="trxDbUniqueId">database row unique id of the target trx that is on clearing</param>
  1731. /// <param name="lockingClientId">the caller identity, the trx will be marked as paid by it, make sure it unique in process
  1732. /// , otherwise and the only impact, you lose the chance to know who really mark this trx with paid in db.</param>
  1733. /// <returns>the trx successfully cleared(marked as state `Paid`) from db, otherwise failed to clear and return null value</returns>
  1734. [UniversalApi(Description = "Clear an unpaid fuel sale trx from db and notify all tcp FdcClients.")]
  1735. public async Task<FuelSaleTransaction> ClearFuelSaleTrxAndNotifyAllFdcClientsAsync(int pumpId, string transactionNo, int trxDbUniqueId, string lockingClientId)
  1736. {
  1737. return await this.ClearFuelSaleTrxAndNotifyAllFdcClientsAsync(pumpId, transactionNo, trxDbUniqueId, lockingClientId, null, -1);
  1738. }
  1739. /// <summary>
  1740. /// Clear an unpaid fuel sale trx from db and notify all tcp FdcClients.
  1741. /// This function is used for Tcp FdcClient caller, the caller most likely a POS which connected in via TCP.
  1742. /// Will return a clearFuelSaleTrx response to tcp Fdc client caller.
  1743. /// </summary>
  1744. /// <param name="pumpId"></param>
  1745. /// <param name="transactionNo"></param>
  1746. /// <param name="trxDbUniqueId"></param>
  1747. /// <param name="workstationID">fdc client's workstation id</param>
  1748. /// <param name="appSenderId">must specify the correct value that used for send respone back via tcp</param>
  1749. /// <param name="fdcClientRequestId">must specify the correct value that used for send respone back via tcp, for pair with request</param>
  1750. /// <returns></returns>
  1751. public async Task<FuelSaleTransaction> ClearFuelSaleTrxAndNotifyAllFdcClientsAsync(int pumpId, string transactionNo,
  1752. int trxDbUniqueId, string workstationID, string appSenderId, int fdcClientRequestId)
  1753. {
  1754. try
  1755. {
  1756. fdcLogger.LogDebug("ClearFuelSaleTrxAndNotifyAllFdcClients (wid: " + workstationID + ", appSenderId: " + (appSenderId ?? "") + ", pumpId: " + pumpId
  1757. + ", transactionNo: " + transactionNo + ", releaseToken: " + trxDbUniqueId + ")");
  1758. SqliteDbContext dbContext = new SqliteDbContext();
  1759. int databaseId = trxDbUniqueId;
  1760. var target = await dbContext.PumpTransactionModels.FirstOrDefaultAsync(t =>
  1761. t.ReleaseToken == databaseId
  1762. && t.PumpId == pumpId
  1763. && t.TransactionSeqNumberFromPhysicalPump == transactionNo);
  1764. if (target != null)
  1765. {
  1766. if (target.State == FuelSaleTransactionState.Paid)
  1767. {
  1768. fdcLogger.LogInformation("ClearFuelSaleTrx for workstationId: " + workstationID + " on pump with pumpId: " + pumpId + ", transactionNo: " + transactionNo
  1769. + ", releaseToken: " + trxDbUniqueId
  1770. + " failed due to target trx is already a Paid trx");
  1771. // null or empty appSenderId indicates the clear request is not send from logged in FdcClient, but from some other source, like
  1772. // litefccCore internal app, then do not reply a response.
  1773. if (!string.IsNullOrEmpty(appSenderId))
  1774. {
  1775. int.TryParse(transactionNo, out int p_trxNo);
  1776. fdcServer.ClearFuelSaleTrx(workstationID, appSenderId, fdcClientRequestId, pumpId, p_trxNo, trxDbUniqueId.ToString(),
  1777. (int)ErrorCode.ERRCD_NOTPOSSIBLE, 1, OverallResult.Failure.ToString());
  1778. }
  1779. return null;
  1780. }
  1781. else
  1782. {
  1783. target.State = FuelSaleTransactionState.Paid;
  1784. target.PaidByFdcClientId = workstationID;
  1785. target.PaidTime = DateTime.Now;
  1786. await dbContext.SaveChangesAsync();
  1787. var targetController = fdcPumpControllers
  1788. .First(c => c.PumpId == pumpId) as IFdcPumpController;
  1789. int.TryParse(transactionNo, out int p_trxNo);
  1790. // null or empty appSenderId indicates the clear request is not send from logged in FdcClient, but from some other source, like
  1791. // litefccCore internal app, then do not reply a response.
  1792. if (!string.IsNullOrEmpty(appSenderId))
  1793. fdcServer.ClearFuelSaleTrx(workstationID, appSenderId, fdcClientRequestId, pumpId, p_trxNo, trxDbUniqueId.ToString(),
  1794. (int)ErrorCode.ERRCD_OK, 1, OverallResult.Success.ToString());
  1795. var safe = this.OnFdcFuelSaleTransactinStateChange;
  1796. safe?.Invoke(this,
  1797. new FdcFuelSaleTransactinStateChangeEventArg(target, FuelSaleTransactionState.Paid));
  1798. var universalApiHub = this.services.GetRequiredService<UniversalApiHub>();
  1799. await universalApiHub.FireEvent(this, "OnFdcFuelSaleTransactinStateChange", new FdcFuelSaleTransactinStateChangeEventArg(target, FuelSaleTransactionState.Paid));
  1800. fdcServer.FuelSaleTrx(target.PumpId,
  1801. target.Volumn / Math.Pow(10, targetController.VolumeDecimalDigits),
  1802. target.Amount / Math.Pow(10, targetController.AmountDecimalDigits),
  1803. target.UnitPrice / Math.Pow(10, targetController.PriceDecimalDigits),
  1804. target.LogicalNozzleId,
  1805. int.Parse(target.ProductBarcode),
  1806. "", "", 1,
  1807. int.Parse(target.TransactionSeqNumberFromPhysicalPump),
  1808. // looks like should return 'Paid', but from old code, we put 'Cleared'
  1809. (int)FuelSaleTransactionState.Cleared,
  1810. 0, trxDbUniqueId.ToString(),
  1811. target.SaleStartTime?.ToString("yyyy-MM-dd HH:mm:ss"),
  1812. target.SaleEndTime?.ToString("yyyy-MM-dd HH:mm:ss"),
  1813. target.LockedByFdcClientId ?? "",
  1814. "", -1, 0);
  1815. return target;
  1816. }
  1817. }
  1818. else
  1819. {
  1820. fdcLogger.LogInformation("ClearFuelSaleTrx for workstationId: " + workstationID + " on pump with pumpId: " + pumpId + ", transactionNo: " + transactionNo
  1821. + ", releaseToken: " + trxDbUniqueId
  1822. + " failed due to target trx could not found");
  1823. // null or empty appSenderId indicates the clear request is not send from logged in FdcClient, but from some other source, like
  1824. // litefccCore internal app, then do not reply a response.
  1825. if (!string.IsNullOrEmpty(appSenderId))
  1826. {
  1827. int.TryParse(transactionNo, out int p_trxNo);
  1828. fdcServer.ClearFuelSaleTrx(workstationID, appSenderId, fdcClientRequestId, pumpId, p_trxNo, trxDbUniqueId.ToString(),
  1829. (int)ErrorCode.ERRCD_BADVAL, 1, OverallResult.Failure.ToString());
  1830. }
  1831. return null;
  1832. }
  1833. }
  1834. catch (Exception exxx)
  1835. {
  1836. fdcLogger.LogInformation("ClearFuelSaleTrx for workstationId: " + workstationID + " on pump with pumpId: " + pumpId + ", transactionNo: " + transactionNo
  1837. + ", releaseToken: " + trxDbUniqueId
  1838. + " failed due to exception: " + exxx);
  1839. return null;
  1840. }
  1841. }
  1842. /// <summary>
  1843. /// Lock an unlocked state fuel sale trx from db and notify all tcp FdcClients.
  1844. /// This function is used for non-tcp-FdcClient caller, which are most likely the Fcc Apps,
  1845. /// so it's a local in-process call.
  1846. /// </summary>
  1847. /// <param name="lockingClientId">the caller identity, the trx will be marked as locked by it, make sure it unique in process
  1848. /// , otherwise, the locked trx may get unlocked unexpectly by other caller with same id.</param>
  1849. /// <param name="pumpId"></param>
  1850. /// <param name="transactionNo"></param>
  1851. /// <param name="trxDbUniqueId"></param>
  1852. /// <returns></returns>
  1853. [UniversalApi(Description = "Lock an unlocked state fuel sale trx from db and notify all tcp FdcClients.")]
  1854. public async Task<FuelSaleTransaction> LockFuelSaleTrxAndNotifyAllFdcClientsAsync(int lockingClientId,
  1855. int pumpId,
  1856. int transactionNo, int trxDbUniqueId)
  1857. {
  1858. return await this.LockFuelSaleTrxAndNotifyAllFdcClientsAsync(lockingClientId, "", -1, pumpId, transactionNo, trxDbUniqueId);
  1859. }
  1860. /// <summary>
  1861. /// Lock an unlocked fuel sale trx from db and notify all tcp FdcClients.
  1862. /// This function is used for tcp FdcClient caller, the caller most likely a POS which connected in via TCP.
  1863. /// Will return a response to tcp Fdc client caller.
  1864. /// </summary>
  1865. /// <param name="lockingClientId">the caller identity, the trx will be marked as locked by it, make sure it unique in process
  1866. /// , otherwise, the locked trx may get unlocked unexpectly by other caller with same id.</param>
  1867. /// <param name="fdcClientAppSender"></param>
  1868. /// <param name="fdcClientRequestId"></param>
  1869. /// <param name="pumpId"></param>
  1870. /// <param name="transactionNo"></param>
  1871. /// <param name="trxDbUniqueId"></param>
  1872. /// <returns></returns>
  1873. public async Task<FuelSaleTransaction> LockFuelSaleTrxAndNotifyAllFdcClientsAsync(int lockingClientId, string fdcClientAppSender, int fdcClientRequestId,
  1874. int pumpId,
  1875. int transactionNo, int trxDbUniqueId)
  1876. {
  1877. try
  1878. {
  1879. fdcLogger.LogDebug("LockFuelSaleTrxAndNotifyAllFdcClients from lockingClientId: " + lockingClientId
  1880. + ", fdcClientAppSender: " + (fdcClientAppSender ?? "")
  1881. + ", fdcClientRequestId: " + fdcClientRequestId
  1882. + ", pumpId: " + pumpId + ", transactionNo: " + transactionNo + ", trxDbUniqueId: " + trxDbUniqueId);
  1883. var result = await FdcResourceArbitrator.Default.TryLockFuelSaleTrxAsync(lockingClientId, pumpId, transactionNo, trxDbUniqueId);
  1884. fdcLogger.LogDebug($" TryLockFuelSaleTrx(trxDbUniqueId: {trxDbUniqueId}) with result: {!(result == null)}");
  1885. //this.LockFuelSaleTrx(int.Parse(workstationID), deviceId, transactionNo, int.Parse(releaseToken));
  1886. if (result != null)
  1887. {
  1888. // null or empty appSenderId indicates the clear request is not send from logged in FdcClient, but from some other source, like
  1889. // litefccCore internal app, then do not reply a response.
  1890. if (!string.IsNullOrEmpty(fdcClientAppSender))
  1891. fdcServer.LockFuelSaleTrx(lockingClientId.ToString(), fdcClientAppSender, fdcClientRequestId, pumpId,
  1892. transactionNo, trxDbUniqueId.ToString(), (int)ErrorCode.ERRCD_OK, 1, OverallResult.Success.ToString());
  1893. var safe = this.OnFdcFuelSaleTransactinStateChange;
  1894. safe?.Invoke(this,
  1895. new FdcFuelSaleTransactinStateChangeEventArg(result, FuelSaleTransactionState.Locked));
  1896. var universalApiHub = this.services.GetRequiredService<UniversalApiHub>();
  1897. await universalApiHub.FireEvent(this, "OnFdcFuelSaleTransactinStateChange", new FdcFuelSaleTransactinStateChangeEventArg(result, FuelSaleTransactionState.Locked));
  1898. var targetController = fdcPumpControllers
  1899. .First(c => c.PumpId == pumpId) as IFdcPumpController;
  1900. fdcServer.FuelSaleTrx(pumpId,
  1901. result.Volumn / Math.Pow(10, targetController.VolumeDecimalDigits),
  1902. result.Amount / Math.Pow(10, targetController.AmountDecimalDigits),
  1903. result.UnitPrice / Math.Pow(10, targetController.PriceDecimalDigits),
  1904. result.LogicalNozzleId,
  1905. int.Parse(result.ProductBarcode),
  1906. this.nozzleExtraInfos
  1907. .FirstOrDefault(n => n.PumpId == pumpId && n.NozzleLogicalId == result.LogicalNozzleId)?.ProductName ??
  1908. ("refer cloud for name of " + result.ProductBarcode), "", 1,
  1909. int.Parse(result.TransactionSeqNumberFromPhysicalPump),
  1910. (int)FuelSaleTransactionState.Locked,
  1911. 0, trxDbUniqueId.ToString(),
  1912. result.SaleStartTime?.ToString("yyyy-MM-dd HH:mm:ss"),
  1913. result.SaleEndTime?.ToString("yyyy-MM-dd HH:mm:ss"),
  1914. lockingClientId.ToString(), "", lockingClientId, 0);
  1915. }
  1916. else
  1917. {
  1918. // null or empty appSenderId indicates the clear request is not send from logged in FdcClient, but from some other source, like
  1919. // litefccCore internal app, then do not reply a response.
  1920. if (!string.IsNullOrEmpty(fdcClientAppSender))
  1921. fdcServer.LockFuelSaleTrx(lockingClientId.ToString(), fdcClientAppSender, fdcClientRequestId, pumpId,
  1922. transactionNo, trxDbUniqueId.ToString(), (int)ErrorCode.ERRCD_TRANSLOCKED, 1, OverallResult.Failure.ToString());
  1923. }
  1924. fdcLogger.LogTrace($" LockFuelSaleTrxAndNotifyAllFdcClients(trxDbUniqueId: {trxDbUniqueId}) returned");
  1925. return result;
  1926. }
  1927. catch (Exception exx)
  1928. {
  1929. fdcLogger.LogError("LockFuelSaleTrxAndNotifyAllFdcClients exceptioned for lockingClientId: "
  1930. + lockingClientId + ", pumpId: " + pumpId + ", transactionNo: " + transactionNo + ", trxDbUniqueId: " + trxDbUniqueId + ", exception detail: " + exx);
  1931. throw;
  1932. }
  1933. }
  1934. /// <summary>
  1935. /// Unlock a locked state fuel sale trx from db and notify all tcp FdcClients.
  1936. /// This function is used for non-tcp-FdcClient caller, which are most likely the Fcc Apps,
  1937. /// so it's a local in-process call.
  1938. /// </summary>
  1939. /// <param name="lockingClientId">the id of the caller when locking the trx, unlocking must be performed by the client who locked the trx</param>
  1940. /// <param name="pumpId"></param>
  1941. /// <param name="transactionNo"></param>
  1942. /// <param name="trxDbUniqueId"></param>
  1943. /// <returns></returns>
  1944. [UniversalApi(Description = "Unlock a locked state fuel sale trx from db and notify all tcp FdcClients.")]
  1945. public async Task<FuelSaleTransaction> UnlockFuelSaleTrxAndNotifyAllFdcClientsAsync(int lockingClientId,
  1946. int pumpId,
  1947. int transactionNo, int trxDbUniqueId)
  1948. {
  1949. return await this.UnlockFuelSaleTrxAndNotifyAllFdcClientsAsync(lockingClientId, "", -1, pumpId, transactionNo, trxDbUniqueId);
  1950. }
  1951. public IEnumerable<NozzleExtraInfo> GetNozzleExtraInfos()
  1952. {
  1953. return this.nozzleExtraInfos;
  1954. }
  1955. /// <summary>
  1956. /// Unlock a locked fuel sale trx from db and notify all tcp FdcClients.
  1957. /// This function is used for tcp FdcClient caller, the caller most likely a POS which connected in via TCP.
  1958. /// Will return a response to tcp Fdc client caller.
  1959. /// </summary>
  1960. /// <param name="unlockingClientId">the id of the caller when locking the trx, unlocking must be performed by the client who locked the trx</param>
  1961. /// <param name="fdcClientAppSender"></param>
  1962. /// <param name="fdcClientRequestId"></param>
  1963. /// <param name="pumpId"></param>
  1964. /// <param name="transactionNo"></param>
  1965. /// <param name="trxDbUniqueId"></param>
  1966. /// <returns></returns>
  1967. public async Task<FuelSaleTransaction> UnlockFuelSaleTrxAndNotifyAllFdcClientsAsync(int unlockingClientId, string fdcClientAppSender, int fdcClientRequestId, int pumpId,
  1968. int transactionNo, int trxDbUniqueId)
  1969. {
  1970. try
  1971. {
  1972. fdcLogger.LogDebug("UnlockFuelSaleTrxAndNotifyAllFdcClients from lockingClientId: " + unlockingClientId
  1973. + ", fdcClientAppSender: " + (fdcClientAppSender ?? "")
  1974. + ", fdcClientRequestId: " + fdcClientRequestId
  1975. + ", pumpId: " + pumpId + ", transactionNo: " + transactionNo + ", trxDbUniqueId: " + trxDbUniqueId);
  1976. var result = await FdcResourceArbitrator.Default.TryUnlockFuelSaleTrxAsync(unlockingClientId, pumpId, transactionNo, trxDbUniqueId);
  1977. if (result != null)
  1978. {
  1979. var targetController = fdcPumpControllers
  1980. .First(c => c.PumpId == pumpId) as IFdcPumpController;
  1981. // null or empty appSenderId indicates the clear request is not send from logged in FdcClient, but from some other source, like
  1982. // litefccCore internal app, then do not reply a response.
  1983. if (!string.IsNullOrEmpty(fdcClientAppSender))
  1984. fdcServer.UnlockFuelSaleTrx(unlockingClientId.ToString(), fdcClientAppSender, fdcClientRequestId, pumpId,
  1985. transactionNo, trxDbUniqueId.ToString(), (int)ErrorCode.ERRCD_OK, 1, OverallResult.Success.ToString());
  1986. var safe = this.OnFdcFuelSaleTransactinStateChange;
  1987. safe?.Invoke(this,
  1988. new FdcFuelSaleTransactinStateChangeEventArg(result, FuelSaleTransactionState.Payable));
  1989. var universalApiHub = this.services.GetRequiredService<UniversalApiHub>();
  1990. await universalApiHub.FireEvent(this, "OnFdcFuelSaleTransactinStateChange", new FdcFuelSaleTransactinStateChangeEventArg(result, FuelSaleTransactionState.Payable));
  1991. fdcServer.FuelSaleTrx(pumpId,
  1992. result.Volumn / Math.Pow(10, targetController.VolumeDecimalDigits),
  1993. result.Amount / Math.Pow(10, targetController.AmountDecimalDigits),
  1994. result.UnitPrice / Math.Pow(10, targetController.PriceDecimalDigits),
  1995. result.LogicalNozzleId,
  1996. int.Parse(result.ProductBarcode),
  1997. this.nozzleExtraInfos
  1998. .FirstOrDefault(n => n.PumpId == pumpId && n.NozzleLogicalId == result.LogicalNozzleId)?.ProductName ??
  1999. ("refer cloud for name of " + result.ProductBarcode), "", 1,
  2000. int.Parse(result.TransactionSeqNumberFromPhysicalPump),
  2001. (int)FuelSaleTransactionState.Payable,
  2002. 0, trxDbUniqueId.ToString(),
  2003. result.SaleStartTime?.ToString("yyyy-MM-dd HH:mm:ss"),
  2004. result.SaleEndTime?.ToString("yyyy-MM-dd HH:mm:ss"),
  2005. "", "", unlockingClientId, 0);
  2006. }
  2007. else
  2008. {
  2009. // null or empty appSenderId indicates the clear request is not send from logged in FdcClient, but from some other source, like
  2010. // litefccCore internal app, then do not reply a response.
  2011. if (!string.IsNullOrEmpty(fdcClientAppSender))
  2012. fdcServer.UnlockFuelSaleTrx(unlockingClientId.ToString(), fdcClientAppSender, fdcClientRequestId, pumpId,
  2013. transactionNo, trxDbUniqueId.ToString(), (int)ErrorCode.ERRCD_TRANSLOCKED, 1, OverallResult.Failure.ToString());
  2014. }
  2015. return result;
  2016. }
  2017. catch (Exception exxx)
  2018. {
  2019. fdcLogger.LogError("UnlockFuelSaleTrxAndNotifyAllFdcClients exceptioned for unlockingClientId: "
  2020. + unlockingClientId + ", pumpId: " + pumpId + ", transactionNo: " + transactionNo + ", trxDbUniqueId: " + trxDbUniqueId + ", exception detail: " + exxx);
  2021. throw;
  2022. }
  2023. }
  2024. /// <summary>
  2025. /// Lock a nozzle by call into a PumpHandler and then notify all tcp FdcClients.
  2026. /// This function is used for non-tcp-FdcClient caller, which are most likely the Fcc Apps,
  2027. /// so it's a local in-process call.
  2028. /// </summary>
  2029. /// <param name="lockingClientId">the caller identity, the trx will be marked as locked by it, make sure it unique in process
  2030. /// , otherwise, the locked trx may get unlocked unexpectly by other caller with same id.</param>
  2031. /// <param name="pumpId"></param>
  2032. /// <param name="logicalNozzleId">logical nozzle in the pump for locking</param>
  2033. /// <returns></returns>
  2034. public async Task<bool> LockNozzleAndNotifyAllFdcClientsAsync(int lockingClientId,
  2035. int pumpId,
  2036. int logicalNozzleId)
  2037. {
  2038. return await this.LockNozzleAndNotifyAllFdcClientsAsync(lockingClientId, "", -1, pumpId, logicalNozzleId);
  2039. }
  2040. /// <summary>
  2041. /// Lock a nozzle by call into a PumpHandler and then notify all tcp FdcClients.
  2042. /// This function is used for tcp FdcClient caller, the caller most likely a POS which connected in via TCP.
  2043. /// Will return a response to tcp Fdc client caller.
  2044. /// </summary>
  2045. /// <param name="lockingClientId">the caller identity, the trx will be marked as locked by it, make sure it unique in process
  2046. /// , otherwise, the locked trx may get unlocked unexpectly by other caller with same id.</param>
  2047. /// <param name="fdcClientAppSender"></param>
  2048. /// <param name="fdcClientRequestId"></param>
  2049. /// <param name="pumpId"></param>
  2050. /// <param name="logicalNozzleId"></param>
  2051. /// <returns></returns>
  2052. public async Task<bool> LockNozzleAndNotifyAllFdcClientsAsync(int lockingClientId, string fdcClientAppSender, int fdcClientRequestId,
  2053. int pumpId,
  2054. int logicalNozzleId)
  2055. {
  2056. try
  2057. {
  2058. fdcLogger.LogDebug("LockNozzleAndNotifyAllFdcClientsAsync from lockingClientId: " + lockingClientId
  2059. + ", fdcClientAppSender: " + (fdcClientAppSender ?? "")
  2060. + ", fdcClientRequestId: " + fdcClientRequestId
  2061. + ", pumpId: " + pumpId + ", logicalNozzleId: " + logicalNozzleId);
  2062. var targetController = fdcPumpControllers
  2063. .First(c => c.PumpId == pumpId) as IFdcPumpController;
  2064. var result = await targetController.LockNozzleAsync((byte)logicalNozzleId);
  2065. if (result)
  2066. {
  2067. // null or empty appSenderId indicates the clear request is not send from logged in FdcClient, but from some other source, like
  2068. // litefccCore internal app, then do not reply a response.
  2069. if (!string.IsNullOrEmpty(fdcClientAppSender))
  2070. fdcServer.LockNozzle(lockingClientId.ToString(), fdcClientAppSender, fdcClientRequestId,
  2071. pumpId, pumpId, logicalNozzleId,
  2072. (int)ErrorCode.ERRCD_OK, OverallResult.Success.ToString());
  2073. }
  2074. else
  2075. {
  2076. fdcLogger.LogDebug("LockNozzleAndNotifyAllFdcClientsAsync failed");
  2077. // null or empty appSenderId indicates the clear request is not send from logged in FdcClient, but from some other source, like
  2078. // litefccCore internal app, then do not reply a response.
  2079. if (!string.IsNullOrEmpty(fdcClientAppSender))
  2080. fdcServer.LockNozzle(lockingClientId.ToString(), fdcClientAppSender, fdcClientRequestId,
  2081. pumpId, pumpId, logicalNozzleId,
  2082. (int)ErrorCode.ERRCD_NOTPOSSIBLE, OverallResult.Failure.ToString());
  2083. }
  2084. return result;
  2085. }
  2086. catch (Exception exx)
  2087. {
  2088. fdcLogger.LogError("LockNozzleAndNotifyAllFdcClientsAsync exceptioned for lockingClientId: "
  2089. + lockingClientId + ", pumpId: " + pumpId + ", logicalNozzleId: " + logicalNozzleId + ", exception detail: " + exx);
  2090. fdcServer.LockNozzle(lockingClientId.ToString(), fdcClientAppSender, fdcClientRequestId,
  2091. pumpId, pumpId, logicalNozzleId,
  2092. (int)ErrorCode.ERRCD_NOTALLOWED, OverallResult.Failure.ToString());
  2093. return false;
  2094. }
  2095. }
  2096. /// <summary>
  2097. /// Unlock a nozzle by call into a PumpHandler and then notify all tcp FdcClients
  2098. /// This function is used for non-tcp-FdcClient caller, which are most likely the Fcc Apps,
  2099. /// so it's a local in-process call.
  2100. /// </summary>
  2101. /// <param name="lockingClientId">the id of the caller when locking the trx, unlocking must be performed by the client who locked the trx</param>
  2102. /// <param name="pumpId"></param>
  2103. /// <param name="logicalNozzleId"></param>
  2104. /// <returns></returns>
  2105. public async Task<bool> UnlockNozzleAndNotifyAllFdcClientsAsync(int lockingClientId,
  2106. int pumpId,
  2107. int logicalNozzleId)
  2108. {
  2109. return await this.UnlockNozzleAndNotifyAllFdcClientsAsync(lockingClientId, "", -1, pumpId, logicalNozzleId);
  2110. }
  2111. /// <summary>
  2112. /// Unlock a nozzle by call into a PumpHandler and then notify all tcp FdcClients
  2113. /// This function is used for tcp FdcClient caller, the caller most likely a POS which connected in via TCP.
  2114. /// Will return a response to tcp Fdc client caller.
  2115. /// </summary>
  2116. /// <param name="unlockingClientId">the id of the caller when locking the trx, unlocking must be performed by the client who locked the trx</param>
  2117. /// <param name="fdcClientAppSender"></param>
  2118. /// <param name="fdcClientRequestId"></param>
  2119. /// <param name="pumpId"></param>
  2120. /// <param name="logicalNozzleId"></param>
  2121. /// <returns></returns>
  2122. public async Task<bool> UnlockNozzleAndNotifyAllFdcClientsAsync(int unlockingClientId, string fdcClientAppSender, int fdcClientRequestId, int pumpId,
  2123. int logicalNozzleId)
  2124. {
  2125. try
  2126. {
  2127. fdcLogger.LogDebug("UnlockNozzleAndNotifyAllFdcClientsAsync from lockingClientId: " + unlockingClientId
  2128. + ", fdcClientAppSender: " + (fdcClientAppSender ?? "")
  2129. + ", fdcClientRequestId: " + fdcClientRequestId
  2130. + ", pumpId: " + pumpId + ", logicalNozzleId: " + logicalNozzleId);
  2131. var targetController = fdcPumpControllers
  2132. .First(c => c.PumpId == pumpId) as IFdcPumpController;
  2133. var result = await targetController.UnlockNozzleAsync((byte)logicalNozzleId);
  2134. if (result)
  2135. {
  2136. // null or empty appSenderId indicates the clear request is not send from logged in FdcClient, but from some other source, like
  2137. // litefccCore internal app, then do not reply a response.
  2138. if (!string.IsNullOrEmpty(fdcClientAppSender))
  2139. fdcServer.UnlockNozzle(unlockingClientId.ToString(), fdcClientAppSender, fdcClientRequestId,
  2140. pumpId, pumpId,
  2141. logicalNozzleId,
  2142. (int)ErrorCode.ERRCD_OK, OverallResult.Success.ToString());
  2143. }
  2144. else
  2145. {
  2146. fdcLogger.LogDebug("UnlockNozzleAndNotifyAllFdcClientsAsync failed");
  2147. // null or empty appSenderId indicates the clear request is not send from logged in FdcClient, but from some other source, like
  2148. // litefccCore internal app, then do not reply a response.
  2149. if (!string.IsNullOrEmpty(fdcClientAppSender))
  2150. fdcServer.UnlockNozzle(unlockingClientId.ToString(), fdcClientAppSender, fdcClientRequestId,
  2151. pumpId, pumpId,
  2152. logicalNozzleId, (int)ErrorCode.ERRCD_NOTPOSSIBLE, OverallResult.Failure.ToString());
  2153. }
  2154. return result;
  2155. }
  2156. catch (Exception exx)
  2157. {
  2158. fdcLogger.LogError("UnlockNozzleAndNotifyAllFdcClientsAsync exceptioned for lockingClientId: "
  2159. + unlockingClientId + ", pumpId: " + pumpId + ", logicalNozzleId: " + logicalNozzleId + ", exception detail: " + exx);
  2160. fdcServer.UnlockNozzle(unlockingClientId.ToString(), fdcClientAppSender, fdcClientRequestId,
  2161. pumpId, pumpId,
  2162. logicalNozzleId, (int)ErrorCode.ERRCD_NOTALLOWED, OverallResult.Failure.ToString());
  2163. return false;
  2164. }
  2165. }
  2166. /// <summary>
  2167. /// Query the totalizer value (money and volume) with decimal point, with default timeout 10 seconds.
  2168. /// </summary>
  2169. /// <param name="pumpId"></param>
  2170. /// <param name="nozzleLogicalId">from 1 to N</param>
  2171. /// <returns>With decimal points value, Money:Volume</returns>
  2172. [UniversalApi(Description = "returned value are With decimal points value, format-> Money:Volume")]
  2173. public async Task<Tuple<double, double>> GetFuelPointTotalsAsync(int pumpId, byte nozzleLogicalId)
  2174. {
  2175. try
  2176. {
  2177. if (fdcLogger.IsEnabled(LogLevel.Debug))
  2178. fdcLogger.LogDebug("GetFuelPointTotals for pumpId: " + pumpId + ", nozzleLogicalId: " + nozzleLogicalId);
  2179. var targetController = fdcPumpControllers.First(c => c.PumpId == pumpId) as IFdcPumpController;
  2180. var totalizerResultTask = targetController.QueryTotalizerAsync(nozzleLogicalId);
  2181. await Task.WhenAny(totalizerResultTask, Task.Delay(10000));
  2182. if (totalizerResultTask.IsCompleted)
  2183. {
  2184. if (fdcLogger.IsEnabled(LogLevel.Debug))
  2185. fdcLogger.LogDebug(" GetFuelPointTotals for pumpId: " + pumpId
  2186. + ", nozzleLogicalId: " + nozzleLogicalId
  2187. + ", no decimal point Money<->Volume pair is: " + totalizerResultTask.Result.Item1 + "<->" + totalizerResultTask.Result.Item2);
  2188. return new Tuple<double, double>(
  2189. totalizerResultTask.Result.Item1 / Math.Pow(10, targetController.AmountDecimalDigits),
  2190. totalizerResultTask.Result.Item2 / Math.Pow(10, targetController.VolumeDecimalDigits));
  2191. }
  2192. else
  2193. {
  2194. fdcLogger.LogInformation(" GetFuelPointTotals for pumpId: " + pumpId
  2195. + ", nozzleLogicalId: " + nozzleLogicalId + " timed out.");
  2196. return new Tuple<double, double>(-1, -1);
  2197. }
  2198. }
  2199. catch (Exception ex)
  2200. {
  2201. fdcLogger.LogError("GetFuelPointTotals for pumpId:" + pumpId +
  2202. ", nozzleLogicalId: " + nozzleLogicalId + " exception:" + ex.Message);
  2203. return new Tuple<double, double>(-1, -1);
  2204. }
  2205. }
  2206. /// <summary>
  2207. /// Query the IFdcPumpController
  2208. /// </summary>
  2209. /// <param name="pumpId"></param>
  2210. /// <returns>IFdcPumpController</returns>
  2211. public IFdcPumpController GetFdcPumpController(int pumpId)
  2212. {
  2213. if (fdcLogger.IsEnabled(LogLevel.Debug))
  2214. fdcLogger.LogDebug("GetFdcPumpController for pumpId: " + pumpId);
  2215. var targetController = fdcPumpControllers
  2216. .First(c => c.PumpId == pumpId) as IFdcPumpController;
  2217. return targetController;
  2218. }
  2219. /// <summary>
  2220. /// Get the fuel sale trxs with details which are in state: Undefined, Payable, or Locked for target Pump.
  2221. /// NOTE, for performance purpose, internal throttle is enabled to only return latest 365 days
  2222. /// or latest 1000 rows of data.
  2223. /// </summary>
  2224. /// <param name="pumpId">the target pump id, or -1 which means the target is all pumps</param>
  2225. /// <returns>list of FuelSaleTransaction, the most recent trx is in more front position</returns>
  2226. public async Task<IEnumerable<FuelSaleTransaction>> GetAvailableFuelSaleTrxsWithDetailsAsync(int pumpId)
  2227. {
  2228. return await this.GetAvailableFuelSaleTrxsWithDetailsAsync(pumpId, -1, 100);
  2229. }
  2230. /// <summary>
  2231. /// Get the fuel sale trxs with details which are in state: Undefined, Payable, or Locked for target Pump.
  2232. /// </summary>
  2233. /// <param name="pumpId">the target pump id, or -1 which means the target is all pumps</param>
  2234. /// <param name="nozzleLogicalId">further specify the nozzle logical id, or -1 which means all nozzles in the pump.</param>
  2235. /// <param name="rowCount">limit the returned row count</param>
  2236. /// <returns>list of FuelSaleTransaction, the most recent trx is in more front position</returns>
  2237. [UniversalApi(Description = "get the latest fuel transactions with state Payable or Locked.")]
  2238. public async Task<IEnumerable<FuelSaleTransaction>> GetAvailableFuelSaleTrxsWithDetailsAsync(int pumpId, int nozzleLogicalId, int rowCount)
  2239. {
  2240. // for limit data size, only show latest N days' data.
  2241. int maxReturnDays = 365;
  2242. // for further limit data size, only show latest N count of data.
  2243. int maxReturnDataCount = rowCount;
  2244. try
  2245. {
  2246. var dueDate = DateTime.Now.Subtract(new TimeSpan(maxReturnDays, 0, 0, 0));
  2247. SqliteDbContext dbContext = new SqliteDbContext();
  2248. if (pumpId == -1)
  2249. {
  2250. if (nozzleLogicalId == -1)
  2251. {
  2252. var all = await dbContext.PumpTransactionModels.Where(t => t.State != FuelSaleTransactionState.Paid
  2253. && t.State != FuelSaleTransactionState.Cleared
  2254. && t.SaleStartTime >= dueDate).OrderByDescending(r => r.SaleStartTime).Take(maxReturnDataCount).ToListAsync();
  2255. return all;
  2256. }
  2257. else
  2258. {
  2259. var all = await dbContext.PumpTransactionModels.Where(t => t.LogicalNozzleId == nozzleLogicalId
  2260. && t.State != FuelSaleTransactionState.Paid
  2261. && t.State != FuelSaleTransactionState.Cleared
  2262. && t.SaleStartTime >= dueDate).OrderByDescending(r => r.SaleStartTime).Take(maxReturnDataCount).ToListAsync();
  2263. return all;
  2264. }
  2265. }
  2266. if (nozzleLogicalId == -1)
  2267. {
  2268. var some = await dbContext.PumpTransactionModels.Where(t => t.PumpId == pumpId
  2269. && t.State != FuelSaleTransactionState.Paid
  2270. && t.State != FuelSaleTransactionState.Cleared
  2271. && t.SaleStartTime >= dueDate).OrderByDescending(r => r.SaleStartTime).Take(maxReturnDataCount).ToListAsync();
  2272. return some;
  2273. }
  2274. else
  2275. {
  2276. var some = await dbContext.PumpTransactionModels.Where(t => t.PumpId == pumpId
  2277. && t.LogicalNozzleId == nozzleLogicalId
  2278. && t.State != FuelSaleTransactionState.Paid
  2279. && t.State != FuelSaleTransactionState.Cleared
  2280. && t.SaleStartTime >= dueDate)
  2281. .OrderByDescending(r => r.SaleStartTime).Take(maxReturnDataCount).ToListAsync();
  2282. return some;
  2283. }
  2284. }
  2285. catch (Exception exxx)
  2286. {
  2287. fdcLogger.LogError("GetAvailableFuelSaleTrxsWithDetails for pumpId: " + pumpId
  2288. + ", nozzleLogicalId: " + nozzleLogicalId
  2289. + ", rowCount: " + rowCount + " exceptioned: "
  2290. + exxx.ToString());
  2291. return null;
  2292. }
  2293. }
  2294. /// <summary>
  2295. /// Get the fuel sale trxes with details by varies of conditions.
  2296. /// </summary>
  2297. /// <param name="pumpId">the target pump id, or -1 for all pumps</param>
  2298. /// <param name="nozzleLogicalId">further specify the nozzle logical id, or -1 for all nozzles in the pump.</param>
  2299. /// <param name="pageRowCount">for paging the returned data, controls how many data rows in a single page</param>
  2300. /// <param name="pageIndex">for paging the returned data, controls return which data page</param>
  2301. /// <param name="filterTimestamp">controls trx that only with its SaleStartTime >= filterTimestamp will be returned, leave null to ignore this check, the results are ordered by SaleStartTime (with ascending).</param>
  2302. /// <returns></returns>
  2303. [UniversalApi(Description = "get the latest fuel transactions by conditions.</br>" +
  2304. "&lt;param name=\"pumpId\"&gt;the target pump id, or null for all pumps&lt;/param&gt;</br>" +
  2305. "&lt;param name=\"nozzleLogicalId\"&gt;further specify the nozzle logical id, or null for all nozzles in the pump.&lt;/param&gt;</br>" +
  2306. "&lt;param name=\"pageRowCount\"&gt;for paging the returned data, controls how many data rows in a single page&lt;/param&gt;</br>" +
  2307. "&lt;param name=\"pageIndex\"&gt;for paging the returned data, controls return which data page&lt;/param&gt;</br>" +
  2308. "&lt;param name=\"filterTimestamp\"&gt;controls trx that only with its SaleStartTime &gt;= filterTimestamp will be returned, leave null to ignore this check, the results are ordered by SaleStartTime (with default ascending)&lt;/param&gt;")]
  2309. public async Task<IEnumerable<FuelSaleTransaction>> GetFuelSaleTrxsWithDetailsAsync(int? pumpId, int? nozzleLogicalId,
  2310. FuelSaleTransactionState transactionState,
  2311. int pageRowCount = 10, int pageIndex = 0, DateTime? filterTimestamp = null, bool isAscending = true)
  2312. {
  2313. // for limit data size, only show latest N days' data.
  2314. int maxReturnDays = 60;
  2315. try
  2316. {
  2317. var dueDate = DateTime.Now.Subtract(new TimeSpan(maxReturnDays, 0, 0, 0));
  2318. if (filterTimestamp != null)
  2319. dueDate = filterTimestamp.Value;
  2320. SqliteDbContext dbContext = new SqliteDbContext();
  2321. if (!pumpId.HasValue)
  2322. {
  2323. IOrderedQueryable<FuelSaleTransaction> orderedResults = null;
  2324. if (isAscending)
  2325. orderedResults = dbContext.PumpTransactionModels.Where(t => t.State == transactionState
  2326. && t.SaleStartTime >= dueDate).OrderBy(r => r.SaleStartTime);
  2327. else
  2328. orderedResults = dbContext.PumpTransactionModels.Where(t => t.State == transactionState
  2329. && t.SaleStartTime >= dueDate).OrderByDescending(r => r.SaleStartTime);
  2330. var all = await orderedResults.Skip(pageRowCount * pageIndex).Take(pageRowCount).ToListAsync();
  2331. return all;
  2332. }
  2333. if (!nozzleLogicalId.HasValue)
  2334. {
  2335. IOrderedQueryable<FuelSaleTransaction> orderedResults = null;
  2336. if (isAscending)
  2337. orderedResults = dbContext.PumpTransactionModels.Where(t => t.PumpId == pumpId
  2338. && t.State == transactionState && t.SaleStartTime >= dueDate).OrderBy(r => r.SaleStartTime);
  2339. else
  2340. orderedResults = dbContext.PumpTransactionModels.Where(t => t.PumpId == pumpId
  2341. && t.State == transactionState && t.SaleStartTime >= dueDate).OrderByDescending(r => r.SaleStartTime);
  2342. var some = await orderedResults.Skip(pageRowCount * pageIndex).Take(pageRowCount).ToListAsync();
  2343. return some;
  2344. }
  2345. else
  2346. {
  2347. IOrderedQueryable<FuelSaleTransaction> orderedResults = null;
  2348. if (isAscending)
  2349. orderedResults = dbContext.PumpTransactionModels.Where(t => t.PumpId == pumpId
  2350. && t.LogicalNozzleId == nozzleLogicalId
  2351. && t.State == transactionState && t.SaleStartTime >= dueDate).OrderBy(r => r.SaleStartTime);
  2352. else
  2353. orderedResults = dbContext.PumpTransactionModels.Where(t => t.PumpId == pumpId
  2354. && t.LogicalNozzleId == nozzleLogicalId
  2355. && t.State == transactionState && t.SaleStartTime >= dueDate).OrderByDescending(r => r.SaleStartTime);
  2356. var some = await orderedResults.Skip(pageRowCount * pageIndex).Take(pageRowCount).ToListAsync();
  2357. return some;
  2358. }
  2359. }
  2360. catch (Exception exxx)
  2361. {
  2362. fdcLogger.LogError($"GetFuelSaleTrxsWithDetailsAsync for pumpId: { pumpId }, nozzleLogicalId: { nozzleLogicalId }, " +
  2363. $"pageRowCount: { pageRowCount }, pageIndex: {pageIndex}, filterTimestamp: {filterTimestamp ?? DateTime.MinValue}, isAscending: {isAscending}, exceptioned:{exxx}");
  2364. return null;
  2365. }
  2366. }
  2367. /// <summary>
  2368. /// Change a price for pumps that have the target barcode product configurated for their nozzles.
  2369. /// </summary>
  2370. /// <param name="barcode">fuel product barcode</param>
  2371. /// <param name="newPriceWithDecimalPoints">new price with decimal points</param>
  2372. /// <returns>the successfully price changed nozzle will be returned</returns>
  2373. [UniversalApi]
  2374. public async Task<IEnumerable<LogicalNozzle>> ChangeFuelPriceAsync(int barcode, double newPriceWithDecimalPoints)
  2375. {
  2376. try
  2377. {
  2378. fdcLogger.LogInformation("ChangeFuelPrice for product with barcode: " + barcode
  2379. + " to new price(with decimal points): " + newPriceWithDecimalPoints);
  2380. var targetNozzles = fdcPumpControllers.SelectMany(p => p.Nozzles).Join(
  2381. nozzleExtraInfos.Where(c => c.ProductBarcode == barcode),
  2382. n => n.PumpId + "," + n.LogicalId,
  2383. c => c.PumpId + "," + c.NozzleLogicalId,
  2384. (n, c) => n);
  2385. if (!targetNozzles.Any())
  2386. {
  2387. fdcLogger.LogError("ChangeFuelPrice, have NOT seen product with barcode: " + barcode
  2388. + " bound to any nozzles from all pumps, will quit.");
  2389. return null;
  2390. }
  2391. //var oldPriceWithNoHumanReadableFormat = targetNozzles.First().RealPriceOnPhysicalPump;
  2392. var succeedNozzles = new List<LogicalNozzle>();
  2393. foreach (var nozzle in targetNozzles)
  2394. {
  2395. bool succeed = false;
  2396. var targetController = fdcPumpControllers.First(p => p.PumpId == nozzle.PumpId);
  2397. var pumpRawFormatPrice = (int)(
  2398. Math.Round(newPriceWithDecimalPoints * Math.Pow(10, targetController.PriceDecimalDigits),
  2399. MidpointRounding.AwayFromZero));
  2400. var previousPrice = targetController.Nozzles
  2401. .First(n => n.LogicalId == nozzle.LogicalId)?.RealPriceOnPhysicalPump;
  2402. //if (previousPrice.HasValue
  2403. // && previousPrice.Value == pumpRawFormatPrice)
  2404. //{
  2405. // succeed = true;
  2406. // fdcLogger.LogInformation(" price change, Pump " + targetController.PumpId + ", NozzleLogicalId " + nozzle.LogicalId + ", new price EQUALS the previous price, will NOT perform ChangeFuelPrice request");
  2407. //}
  2408. //else
  2409. //{
  2410. FuelPriceChange dbFuelPriceChange = null;
  2411. try
  2412. {
  2413. dbFuelPriceChange = new FuelPriceChange()
  2414. {
  2415. PumpId = nozzle.PumpId,
  2416. LogicalNozzleId = nozzle.LogicalId,
  2417. NewPriceWithoutDecimal = pumpRawFormatPrice,
  2418. StartTime = DateTime.Now,
  2419. FinishTime = null
  2420. };
  2421. var dbContext = new SqliteDbContext();
  2422. dbContext.FuelPriceChanges.Add(dbFuelPriceChange);
  2423. await dbContext.SaveChangesAsync();
  2424. //fdcLogger.LogDebug(" ChangeFuelPrice, new price for pump: " + targetController.PumpId + " pre-save into database succeed");
  2425. }
  2426. catch (Exception exxx)
  2427. {
  2428. fdcLogger.LogError(" ChangeFuelPrice, new price pre-save into database for pump: "
  2429. + targetController.PumpId + ", logical Nozzle: " + nozzle.LogicalId
  2430. + " exceptioned: " + exxx
  2431. + Environment.NewLine
  2432. + "Will ignore this nozzle and continue to next...");
  2433. continue;
  2434. }
  2435. try
  2436. {
  2437. succeed = await targetController.ChangeFuelPriceAsync(pumpRawFormatPrice, nozzle.LogicalId);
  2438. fdcLogger.LogInformation(" ChangeFuelPrice succeed in IFdcController side for pump with PumpId: " + targetController.PumpId
  2439. + ", LogicalNozzleId: " + nozzle.LogicalId);
  2440. }
  2441. catch (Exception exxx)
  2442. {
  2443. fdcLogger.LogError(" ChangeFuelPrice, pump " + targetController.PumpId
  2444. + ", logical nozzle " + nozzle.LogicalId + " exceptioned in pump side: "
  2445. + exxx
  2446. + Environment.NewLine
  2447. + "Will continue to next nozzle.");
  2448. continue;
  2449. }
  2450. if (succeed)
  2451. {
  2452. succeedNozzles.Add(nozzle);
  2453. //fdcLogger.LogDebug("ChangeFuelPrice for pump: " + targetController.PumpId
  2454. // + ", logicalNozzle: " + nozzle.LogicalId + " succeed on pump side, will post-save to db");
  2455. try
  2456. {
  2457. dbFuelPriceChange.FinishTime = DateTime.Now;
  2458. var dbContext = new SqliteDbContext();
  2459. await dbContext.SaveChangesAsync();
  2460. }
  2461. catch (Exception exxx)
  2462. {
  2463. fdcLogger.LogError(" ChangeFuelPrice, new price post-save into database for pump: "
  2464. + targetController.PumpId
  2465. + ", logicalNozzle: " + nozzle.LogicalId + " exceptioned: "
  2466. + exxx
  2467. + Environment.NewLine
  2468. + "Will ignore and continue to next nozzle");
  2469. }
  2470. }
  2471. else
  2472. {
  2473. fdcLogger.LogError(" ChangeFuelPrice, Pump " + targetController.PumpId
  2474. + ", NozzleLogicalId " + nozzle.LogicalId + " apply new price failed on pump side");
  2475. }
  2476. }
  2477. return succeedNozzles;
  2478. }
  2479. catch (Exception exxx)
  2480. {
  2481. fdcLogger.LogError("ChangeFuelPrice, generic exception: " + exxx);
  2482. return null;
  2483. }
  2484. }
  2485. #endregion
  2486. }
  2487. }