FdcServerHostAppDelayAuth.cs 160 KB

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