IPosPlusApp.cs 36 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946
  1. using Edge.Core.Processor;
  2. using Edge.Core.IndustryStandardInterface.Pump;
  3. using Dfs.WayneChina.HengshanPayTerminal;
  4. using Dfs.WayneChina.HengshanPayTerminal.MessageEntity;
  5. using Dfs.WayneChina.HengshanPayTerminal.MessageEntity.Incoming;
  6. using System;
  7. using System.Collections.Generic;
  8. using System.Text;
  9. using System.Linq;
  10. using Dfs.WayneChina.HengshanPayTerminal.Support;
  11. using Applications.FDC;
  12. using Edge.Core.Database;
  13. using FdcServerHost;
  14. using Dfs.WayneChina.CardTrxManager.TrxSubmitter;
  15. using Dfs.WayneChina.CardTrxManager;
  16. using System.Threading.Tasks;
  17. using Dfs.WayneChina.IPosPlus.ServiceClient;
  18. using Dfs.WayneChina.SpsDataCourier;
  19. using NLog.LayoutRenderers.Wrappers;
  20. using Edge.Core.Configuration;
  21. using Edge.Core.Processor.Dispatcher.Attributes;
  22. namespace Dfs.WayneChina.IPosPlus
  23. {
  24. /// <summary>
  25. /// Serves as an application controller containing the terminal handlers and terminal managers,
  26. /// as well as the database access manager.
  27. /// </summary>
  28. [MetaPartsDescriptor(
  29. "lang-zh-cn:iPosPlus应用lang-en-us:iPOS Plus App",
  30. "lang-zh-cn:用于管理加油机IC卡终端控制" +
  31. "lang-en-us:Used for managing all IC card terminals on dispensers across the site",
  32. new[] { "lang-zh-cn:加油机lang-en-us:Pump", "lang-zh-cn:IfsfFdcServerlang-en-us:IfsfFdcServer" })]
  33. public class IPosPlusApp : IAppProcessor
  34. {
  35. #region Fields
  36. private SpsDbManager.SpsManager spsManager;
  37. private List<HengshanPayTermHandler> paymentTerminalHandlers = new List<HengshanPayTermHandler>();
  38. private List<TerminalManager> terminalManagers = new List<TerminalManager>();
  39. private NLog.Logger logger = NLog.LogManager.LoadConfiguration("NLog.config").GetLogger("IPosPlusApp");
  40. private PosInitialData initalData;
  41. private object syncObj = new object();
  42. private FdcServerHostApp fdcServerApp;
  43. private string fuelProductMapping;
  44. private IEnumerable<NozzleExtraInfo> nozzleProductConfig = Configurator.Default.NozzleExtraInfoConfiguration.Mapping;
  45. private Dictionary<int, int> fuelMappingDict = new Dictionary<int, int>();
  46. private List<FillingInfo> customerFillings = new List<FillingInfo>();
  47. private List<TrxSubmitter> submitters = new List<TrxSubmitter>();
  48. private CloudCredential cloudCredential;
  49. private bool onlineDiscount = false;
  50. private int discountTimeout = 3;
  51. private string promotionCategories = string.Empty;
  52. private Dictionary<int, string> fuelNameDict = new Dictionary<int, string>();
  53. private Dictionary<string, uint> fuelCodePriceDict = new Dictionary<string, uint>();
  54. public int CardAppType { get; }
  55. private int interval;
  56. private Dictionary<int, bool> pumpPriceDict = new Dictionary<int, bool>();
  57. private object dictSync = new object();
  58. private SpsDataCourierApp spsDataCourierApp;
  59. private StoreForwardManager sfManager;
  60. private int stationNo = 0;
  61. private string stationName = "";
  62. private int mode = 1;
  63. private int reservedBalance = 20;
  64. #endregion
  65. #region MySQL connection string
  66. private string _mysqlConn =
  67. "server=localhost;port=3306;user=root;password=HS1205;database=sps_db;TreatTinyAsBoolean=false;Convert Zero Datetime=True";
  68. #endregion
  69. #region Constructor
  70. [ParamsJsonSchemas("appCtorParamsJsonSchema")]
  71. public IPosPlusApp(PosAppContructorParameterV1 config)
  72. {
  73. PosId = config.PosId;
  74. _mysqlConn = FormatConnectionString(config.ConnectionString);
  75. spsManager = new SpsDbManager.SpsManager(_mysqlConn);
  76. //Current Business Unit Id.
  77. var fakeBuId = Configurator.Default
  78. .MetaConfiguration
  79. .Parameter?
  80. .FirstOrDefault(p => p.Name.Equals("serialNumber", StringComparison.OrdinalIgnoreCase))?.Value;
  81. cloudCredential = new CloudCredential
  82. {
  83. UserName = config.Username,
  84. Password = config.Password,
  85. AuthServiceBaseUrl = config.AuthServiceBaseUrl,
  86. TransactionServiceBaseUrl = config.TransactionServiceBaseUrl,
  87. DiscountServiceBaseUrl = config.DiscountServiceBaseUrl,
  88. DeviceSN = config.DeviceSN,
  89. CurrentBuId = fakeBuId
  90. };
  91. onlineDiscount = config.OnlineDiscount;
  92. discountTimeout = config.DiscountTimeout;
  93. if (this.onlineDiscount)
  94. {
  95. DiscountServiceClient = new DiscountServiceClient(this, cloudCredential, promotionCategories, discountTimeout);
  96. }
  97. promotionCategories = config.PromoCategories;
  98. fuelProductMapping = string.Join(";", config.FuelMappingArr.Select(m => $"{m.Barcode}:{m.FuelNo}"));
  99. CardAppType = config.CardAppType;
  100. interval = config.Interval;
  101. mode = config.PumpMode;
  102. stationNo = config.StationNo;
  103. stationName = config.StationName;
  104. reservedBalance = config.ReservedBalance;
  105. SetFuelProductMapping();
  106. pumpPriceDict.Clear();
  107. sfManager = new StoreForwardManager(cloudCredential);
  108. }
  109. public IPosPlusApp(int posId, string username, string password, string authServiceBaseUrl, string transactionServiceBaseUrl,
  110. string discountServiceBaseUrl, bool onlineDiscount, int discountTimeout, string deviceSN, string promoCategories, List<FuelMappingV1> fuelMappingArr,/*string fuelMapping,*/
  111. int cardAppType, int reservedBalance, int interval, SpsDbConnectionSetting connectionString)
  112. {
  113. PosId = posId;
  114. _mysqlConn = FormatConnectionString(connectionString);//connectionString;
  115. spsManager = new SpsDbManager.SpsManager(_mysqlConn);
  116. //Current Business Unit Id.
  117. var fakeBuId = Configurator.Default
  118. .MetaConfiguration
  119. .Parameter?
  120. .FirstOrDefault(p => p.Name.Equals("serialNumber", StringComparison.OrdinalIgnoreCase))?.Value;
  121. cloudCredential = new CloudCredential
  122. {
  123. UserName = username,
  124. Password = password,
  125. AuthServiceBaseUrl = authServiceBaseUrl,
  126. TransactionServiceBaseUrl = transactionServiceBaseUrl,
  127. DiscountServiceBaseUrl = discountServiceBaseUrl,
  128. DeviceSN = deviceSN,
  129. CurrentBuId = fakeBuId
  130. };
  131. this.onlineDiscount = onlineDiscount;//Convert.ToBoolean(onlineDiscount);
  132. this.discountTimeout = discountTimeout;
  133. promotionCategories = promoCategories;
  134. if (this.onlineDiscount)
  135. {
  136. DiscountServiceClient = new DiscountServiceClient(this, cloudCredential, promotionCategories, discountTimeout);
  137. }
  138. fuelProductMapping = string.Join(";", fuelMappingArr.Select(m => $"{m.Barcode}:{m.FuelNo}")); //fuelMapping;
  139. CardAppType = cardAppType;
  140. this.interval = interval;
  141. this.reservedBalance = reservedBalance;
  142. SetFuelProductMapping();
  143. pumpPriceDict.Clear();
  144. sfManager = new StoreForwardManager(cloudCredential);
  145. }
  146. private string FormatConnectionString(SpsDbConnectionSetting spsDbConnectionSetting)
  147. {
  148. return $"server={spsDbConnectionSetting.Server};port={spsDbConnectionSetting.Port};uid={spsDbConnectionSetting.Username};password={spsDbConnectionSetting.Password};database=sps_db;TreatTinyAsBoolean=false;Convert Zero Datetime=true;";
  149. }
  150. #endregion
  151. #region IApplication interface implementation
  152. public string MetaConfigName { get; set; }
  153. public void Init(IEnumerable<IProcessor> processors)
  154. {
  155. foreach (dynamic p in processors)
  156. {
  157. if (p is IAppProcessor)
  158. {
  159. FdcServerHostApp fdcServer = p as FdcServerHostApp;
  160. if (fdcServer != null)
  161. fdcServerApp = p;
  162. SpsDataCourierApp dataCourier = p as SpsDataCourierApp;
  163. if (dataCourier != null)
  164. spsDataCourierApp = dataCourier;
  165. continue;
  166. }
  167. var handler = p.Context.Handler;
  168. if (handler is HengshanPayTermHandler)
  169. {
  170. paymentTerminalHandlers.Add(handler);
  171. }
  172. }
  173. SetupTerminalManagers();
  174. SetupTransactionSubmitters();
  175. GetInitialData();
  176. GetStationInfo();
  177. if (fdcServerApp != null)
  178. {
  179. fdcServerApp.OnCurrentFuellingStatusChange += FdcServerApp_OnCurrentFuellingStatusChange;
  180. }
  181. }
  182. private bool UpsertSpsConfig()
  183. {
  184. if (fdcServerApp == null)
  185. {
  186. logger.Error($"Could not get FdcServer instance");
  187. return false;
  188. }
  189. var allNozzleExtraInfo = fdcServerApp.GetNozzleExtraInfos();
  190. var productNames = allNozzleExtraInfo.Select(n => n.ProductName).Distinct().ToList();
  191. //filter out the product names
  192. foreach (var item in productNames)
  193. {
  194. var tank = allNozzleExtraInfo.FirstOrDefault(n => n.ProductName == item)?.TankNumber;
  195. logger.Info($"Distinct product name: {item}, related tank: {tank}");
  196. }
  197. //filter out the tanks
  198. var tanks = allNozzleExtraInfo.Select(n => n.TankNumber).Distinct();
  199. foreach (var item in tanks)
  200. {
  201. logger.Info($"Distinct tank number: {item}");
  202. }
  203. var siteProducts = allNozzleExtraInfo.Select(n => new { Barcode = n.ProductBarcode, Name = n.ProductName }).ToList();
  204. var fuelBarcodes = allNozzleExtraInfo.Select(n => n.ProductBarcode).Distinct().ToList();
  205. if (fuelBarcodes.Count > tanks.Count())
  206. {
  207. logger.Error($"More fuel product types, less tanks, abort!");
  208. return false;
  209. }
  210. foreach (var product in siteProducts)
  211. {
  212. logger.Info($"Update product: {product.Barcode}, {product.Name}");
  213. var fuelCode = fuelMappingDict[product.Barcode]; // Code for fuel product, e.g. 1021, 2001
  214. string fuelClassNo = "1000";
  215. if (fuelCode >= 1000 && fuelCode < 2000)
  216. fuelClassNo = "1000";
  217. else if (fuelCode >= 2000)
  218. fuelClassNo = "2000";
  219. spsManager.AddOrUpdateFuelProduct(Convert.ToString(fuelCode), fuelClassNo, product.Name, 999);
  220. }
  221. //Tank product
  222. var tankProducts = new Dictionary<int, string>();
  223. foreach (var tank in tanks)
  224. {
  225. //product barcode for the tank
  226. var productBarcode = allNozzleExtraInfo.First(n => n.TankNumber == tank.Value).ProductBarcode;
  227. if (!tankProducts.ContainsKey(tank.Value))
  228. {
  229. string fuelNo = Convert.ToString(fuelMappingDict[productBarcode]);
  230. logger.Info($"Add to tankProducts Dict, tank.value: {tank.Value}, fuelno: {fuelNo}");
  231. tankProducts.Add(tank.Value, fuelNo); // tank_no, fuel_no
  232. }
  233. }
  234. foreach (var item in tankProducts)
  235. {
  236. spsManager.AddOrUpdateTankConfig(item.Key, item.Value);
  237. logger.Info($"add or update tank config: {item.Key}, {item.Value}");
  238. }
  239. foreach (var tankProduct in tankProducts)
  240. {
  241. logger.Info($"tank id: {tankProduct.Key}, tank product code: {tankProduct.Value}");
  242. }
  243. // filter out the pumps
  244. var pumps = allNozzleExtraInfo.Select(n => n.PumpId).Distinct();
  245. foreach (var pump in pumps)
  246. {
  247. logger.Info($"Distinct pump id: {pump}");
  248. foreach (var ptHandler in paymentTerminalHandlers)
  249. {
  250. if (ptHandler.AssociatedPumpIds.Contains(pump))
  251. {
  252. int serialPort = 4;
  253. string portName = ptHandler.CommIdentity;
  254. logger.Info($"Communicator identity: {portName}");
  255. if (!string.IsNullOrEmpty(portName))
  256. {
  257. string name = "";
  258. for (int i = 0; i < portName.Length; i++)
  259. {
  260. if (Char.IsDigit(portName[i]))
  261. name += portName[i];
  262. }
  263. if (name.Length > 0)
  264. {
  265. int.TryParse(name, out serialPort);
  266. if (serialPort == 0)
  267. serialPort = 4;
  268. }
  269. if (portName.Contains('.'))
  270. {
  271. string[] sections = portName.Split(':');
  272. name = sections.LastOrDefault();
  273. int.TryParse(name, out serialPort);
  274. int originalPort = serialPort;
  275. serialPort = originalPort % 100;
  276. logger.Info($"Parsed with dot, original: {originalPort} serialPort: {serialPort}");
  277. if (serialPort == 0)
  278. serialPort = 4;
  279. }
  280. }
  281. else
  282. {
  283. serialPort = ptHandler.AssociatedPumpIds.Max();
  284. }
  285. var subAddress = ptHandler.GetSubAddressForPump(pump);
  286. logger.Info($"Sub Address: {subAddress}, COM: {serialPort} for Pump: {pump}");
  287. spsManager.AddOrUpdatePump(mode, Convert.ToByte(pump), serialPort, Convert.ToByte(subAddress), 999999);
  288. logger.Info($"Updating pump: {pump}, sub address: {subAddress}");
  289. }
  290. }
  291. }
  292. foreach (var nozzleExtraInfo in allNozzleExtraInfo)
  293. {
  294. logger.Info($"PumpId: {nozzleExtraInfo.PumpId}, NozzleLogicalId: {nozzleExtraInfo.NozzleLogicalId}, " +
  295. $"SiteLevelNozzleId: {nozzleExtraInfo.SiteLevelNozzleId}, ProductBarcode: {nozzleExtraInfo.ProductBarcode}, " +
  296. $"ProductName: {nozzleExtraInfo.ProductName}, TankNumber: {nozzleExtraInfo.TankNumber}");
  297. byte nozzleId = Convert.ToByte(nozzleExtraInfo.SiteLevelNozzleId);
  298. byte pumpId = Convert.ToByte(nozzleExtraInfo.PumpId);
  299. byte tankId = Convert.ToByte(nozzleExtraInfo.TankNumber);
  300. string fuelNo = tankProducts[nozzleExtraInfo.TankNumber.Value];
  301. spsManager.AddOrUpdateNozzle(nozzleId, pumpId, tankId, fuelNo);
  302. logger.Info($"Updating NozzleId: {nozzleId}, PumpId: {pumpId}, TankId: {tankId}, FuelNo: {fuelNo}");
  303. }
  304. spsManager.UpdateGeneralInfoVersion();
  305. spsManager.UpdateFuelPriceVersion();
  306. logger.Info($"Updating station no: {stationNo}, station name: {stationName}");
  307. spsManager.UpdateStationInfo(stationNo, stationName);
  308. return true;
  309. }
  310. private void FdcServerApp_OnCurrentFuellingStatusChange(object sender, FdcServerTransactionDoneEventArg e)
  311. {
  312. if (e.Transaction.Finished)
  313. {
  314. InfoLog($"There is a finished filling, ReleaseToken: {e.ReleaseToken}, PumpId: {e.Transaction.Nozzle.PumpId}," +
  315. $" LogicalNozzleId: {e.Transaction.Nozzle.LogicalId}, SequenceNo: {e.Transaction.SequenceNumberGeneratedOnPhysicalPump}");
  316. Task.Run(async () => await HandleCustomerCardFillings(e));
  317. }
  318. }
  319. //初步定位在这触发
  320. private async Task HandleCustomerCardFillings(FdcServerTransactionDoneEventArg e)
  321. {
  322. int nozzleNoForCurrentTrx = e.Transaction.Nozzle.PumpId;
  323. byte currentPumpId = Convert.ToByte(e.Transaction.Nozzle.PumpId);
  324. var pth = paymentTerminalHandlers.First(h => h.AssociatedPumpIds.Contains(currentPumpId));
  325. if (pth != null)
  326. {
  327. var nozzleNos = pth.PumpSiteNozzleNoDict[currentPumpId];
  328. foreach (var item in nozzleNos)
  329. {
  330. if (pth.NozzleLogicIdDict[item] == e.Transaction.Nozzle.LogicalId)
  331. {
  332. nozzleNoForCurrentTrx = item;
  333. }
  334. }
  335. }
  336. //调整当前加油记录List 加上日期的筛选 -45天前的记录
  337. var filling = customerFillings.OrderByDescending(p => p.StartTime).FirstOrDefault(f => f.SequenceNo == e.Transaction.SequenceNumberGeneratedOnPhysicalPump &&
  338. f.PumpId == e.Transaction.Nozzle.PumpId && f.NozzleId == nozzleNoForCurrentTrx && f.StartTime > DateTime.Now.AddDays(-45));
  339. if (filling != null)
  340. {
  341. InfoLog("Locking transaction");
  342. var lockedTrx = await fdcServerApp.LockFuelSaleTrxAndNotifyAllFdcClientsAsync(100, e.Transaction.Nozzle.PumpId, filling.SequenceNo, e.ReleaseToken.Value);
  343. if (lockedTrx == null)
  344. {
  345. InfoLog("Amazing, failed to lock transaction in FdcServer, try again in 100 ms");
  346. await Task.Delay(100);
  347. lockedTrx = await fdcServerApp.LockFuelSaleTrxAndNotifyAllFdcClientsAsync(100, e.Transaction.Nozzle.PumpId, filling.SequenceNo, e.ReleaseToken.Value);
  348. if (lockedTrx == null)
  349. {
  350. InfoLog("Locking transaction failed in second try");
  351. await Task.Delay(100);
  352. lockedTrx = await fdcServerApp
  353. .LockFuelSaleTrxAndNotifyAllFdcClientsAsync(100, e.Transaction.Nozzle.PumpId, filling.SequenceNo, e.ReleaseToken.Value);
  354. }
  355. }
  356. InfoLog("Ready to submit the customer card transaction!");
  357. var submitter = GetSubmitter(filling.PumpId);
  358. if (submitter != null)
  359. {
  360. InfoLog("Grabbed instance of submitter");
  361. var cardAccountInfo = spsManager.GetCardAccountInfo(filling.CardNo);
  362. var result = await submitter.SubmitTrxAsync(new ClientTrxInfo
  363. {
  364. PumpId = filling.PumpId,
  365. NozzleId = 1,
  366. SiteNozzleNo = filling.NozzleId,//filling.PumpId,
  367. Barcode = GetBarcode(filling.FuelProductCode),
  368. Volume = Convert.ToDecimal(filling.Volume) / 100,
  369. Amount = Convert.ToDecimal(filling.FillingAmount) / 100,
  370. PayAmount = Convert.ToDecimal(filling.PayAmount) / 100,
  371. UnitPrice = Convert.ToDecimal(filling.UnitPrice) / 100,
  372. CardNo = filling.CardNo,
  373. CurrentCardBalance = Convert.ToDecimal(filling.CardBalance) / 100,
  374. FdcSqNo = e.ReleaseToken.Value,
  375. SeqNo = filling.SequenceNo,
  376. FuelingStartTime = filling.StartTime,
  377. FuelingFinishedTime = filling.EndTime,
  378. VolumeTotalizer = Convert.ToDecimal(filling.VolumeTotal) / 100,
  379. CardHolder = cardAccountInfo?.Holder,
  380. AccountName = cardAccountInfo?.BelongTo
  381. });
  382. InfoLog($"Submit successfully? {result}");
  383. if (result)
  384. {
  385. InfoLog("Clearing transaction");
  386. await fdcServerApp.ClearFuelSaleTrxAndNotifyAllFdcClientsAsync(e.Transaction.Nozzle.PumpId,
  387. filling.SequenceNo.ToString(), e.ReleaseToken.Value, "100");
  388. //After uploading, remove it.
  389. customerFillings.Remove(filling);
  390. }
  391. }
  392. }
  393. }
  394. public void AddCustomerCardFilling(FillingInfo fillingInfo)
  395. {
  396. customerFillings.Add(fillingInfo);
  397. }
  398. private void GetInitialData()
  399. {
  400. var result = spsManager.GetInitialData(PosId);
  401. if (result != null)
  402. {
  403. initalData = new PosInitialData
  404. {
  405. BillNo = result.Bill_No,
  406. ShiftNo = result.Shift_No,
  407. ShiftState = result.Shift_State
  408. };
  409. }
  410. }
  411. private void GetStationInfo()
  412. {
  413. var stationData = spsManager.GetStationInfo();
  414. if (stationData != null)
  415. {
  416. StationInfo = new StationInfo { StationNo = stationData.Sno };
  417. }
  418. }
  419. private void UpdateStationInfo(int stationNo, string name)
  420. {
  421. spsManager.UpdateStationInfo(stationNo, name);
  422. }
  423. public int GetNextBillNo()
  424. {
  425. lock (syncObj)
  426. {
  427. return ++initalData.BillNo;
  428. }
  429. }
  430. public Task<bool> Start()
  431. {
  432. //Hook up the terminal message event handler
  433. foreach (var handler in paymentTerminalHandlers)
  434. {
  435. handler.OnTerminalMessageReceived += Handler_OnTerminalMessageReceived;
  436. handler.OnFuelPriceChangeRequested += Handler_OnFuelPriceChangeRequested;
  437. }
  438. InitFuelName();
  439. InitFuelPrices();
  440. if (spsDataCourierApp != null)
  441. logger.Info("SpsDataCourier instance exists!");
  442. spsManager.Start();
  443. sfManager.Start();
  444. sfManager.OnStoreForwardCompleted += SfManagerStoreForwardCompleted;
  445. // Inserting configuration data into sps_db.
  446. var result = UpsertSpsConfig();
  447. if (result == false)
  448. return Task.FromResult(false);
  449. return Task.FromResult(true);
  450. }
  451. private async void SfManagerStoreForwardCompleted(object sender, StoreForwardCompletedEventArgs e)
  452. {
  453. logger.Info($"SF completed, clear transaction, pump id: {e.PumpdId}, sq no: {e.SeqNo}, rt: {e.ReleaseToken}");
  454. await fdcServerApp.ClearFuelSaleTrxAndNotifyAllFdcClientsAsync(e.PumpdId, e.SeqNo.ToString(), e.ReleaseToken, "100");
  455. }
  456. private void Handler_OnFuelPriceChangeRequested(object sender, FuelPriceChangeRequestEventArgs e)
  457. {
  458. logger.Info($"PosApp: {e.PumpId}, {e.NozzleId}, {e.Price}");
  459. var result = spsManager.GetFuelPriceConfig();
  460. var fdcServerNozzleProductConfig = fdcServerApp.GetNozzleExtraInfos();
  461. var nozzle = fdcServerNozzleProductConfig.First(c => c.PumpId == e.PumpId && c.NozzleLogicalId == e.NozzleId);
  462. logger.Info($"PosApp: Nozzle, barcode: {nozzle.ProductBarcode}");
  463. var fuelNo = GetFuelNo(nozzle.ProductBarcode);
  464. logger.Info($"PosApp: Got Fuel No: {fuelNo}");
  465. spsManager.UpdateFuelPrice(Convert.ToString(fuelNo), e.Price);
  466. lock (dictSync)
  467. {
  468. pumpPriceDict[e.PumpId] = true;
  469. if (pumpPriceDict.Count(p => p.Value == false) > 1)
  470. {
  471. return;
  472. }
  473. else if (pumpPriceDict.Count(p => p.Value == false) == 1)
  474. {
  475. logger.Info("PosApp: FuelPrice dict, only one pump left, update price version");
  476. spsManager.UpdateFuelPriceVersion();
  477. }
  478. else
  479. {
  480. // After all pumps' requests handled, reset the dictionary
  481. foreach (var item in pumpPriceDict.Keys.ToList())
  482. {
  483. pumpPriceDict[item] = false;
  484. }
  485. }
  486. }
  487. InitFuelPrices();
  488. }
  489. public Task<bool> Stop()
  490. {
  491. //Unhook the terminal message event handler
  492. foreach (var handler in paymentTerminalHandlers)
  493. {
  494. handler.OnTerminalMessageReceived -= Handler_OnTerminalMessageReceived;
  495. handler.OnFuelPriceChangeRequested -= Handler_OnFuelPriceChangeRequested;
  496. }
  497. if (fdcServerApp != null)
  498. {
  499. fdcServerApp.OnCurrentFuellingStatusChange -= FdcServerApp_OnCurrentFuellingStatusChange;
  500. }
  501. return Task.FromResult(true);
  502. }
  503. public void LockUnlockPump(int pumpId, LockUnlockOperation operation)
  504. {
  505. var terminalManager = GetTerminalManager(pumpId);
  506. terminalManager.LockUnlockPump(operation);
  507. }
  508. private async void Handler_OnTerminalMessageReceived(object sender, TerminalMessageEventArgs e)
  509. {
  510. await HandleTerminalMessage(e);
  511. }
  512. private async Task HandleTerminalMessage(TerminalMessageEventArgs e)
  513. {
  514. var message = e.Message;
  515. DebugLog($"Incoming terminal message, HandlerGroup:[{e.Identifier}], Source {message.SourceAddress}, " +
  516. $"Destination {message.DestinationAddress}, FrameSeqNoByte {message.FrameSqNoByte}");
  517. var terminalManager = GetTerminalManager(e.Identifier, message.DestinationAddress);
  518. if (terminalManager == null)
  519. {
  520. logger.Error($"Could not find TerminalManager for message of identifier: {e.Identifier}, dest addr: {message.DestinationAddress}");
  521. return;
  522. }
  523. DebugLog($"Terminal Manager {terminalManager.PumpId} handle the message");
  524. if (message is RegisterRequest)
  525. {
  526. terminalManager.HandleRegiser((RegisterRequest)message);
  527. DebugLog($"RegisterRequest handled, Source {message.SourceAddress}, Destination {message.DestinationAddress}");
  528. }
  529. else if (message is CheckCmdRequest)
  530. {
  531. DebugLog($"CheckCmdRequest, Source {message.SourceAddress}, Destination {message.DestinationAddress}");
  532. terminalManager.HandleCheckCmdRequest((CheckCmdRequest)message);
  533. DebugLog("Sent checkcmdResponse");
  534. }
  535. else if (message is ValidateCardRequest)
  536. {
  537. terminalManager.HandleCardValidation((ValidateCardRequest)message);
  538. DebugLog($"ValidateCardRequest, Source {message.SourceAddress}, Destination {message.DestinationAddress}");
  539. }
  540. else if (message is AuthRequest)
  541. {
  542. terminalManager.HandlAuthorization((AuthRequest)message);
  543. DebugLog($"AuthRequest handled, Source {message.SourceAddress}, Destination {message.DestinationAddress}");
  544. }
  545. else if (message is FuelingDataRequest)
  546. {
  547. terminalManager.HandleFuelingData((FuelingDataRequest)message);
  548. DebugLog($"FuelingDataRequest handled, Source {message.SourceAddress}, Destination {message.DestinationAddress}");
  549. }
  550. else if (message is PaymentRequest)
  551. {
  552. terminalManager.HandlePaymentRequest((PaymentRequest)message);
  553. DebugLog($"PaymentRequest handled, Source {message.SourceAddress}, Destination {message.DestinationAddress}");
  554. }
  555. else if (message is TransactionDataRequest)
  556. {
  557. await terminalManager.HandleTransactionData((TransactionDataRequest)message);
  558. DebugLog($"TransactionDataRequest handled, Source {message.SourceAddress}, Destination {message.DestinationAddress}");
  559. }
  560. else if (message is LockOrUnlockPumpAck)
  561. {
  562. terminalManager.HandleLockUnlockPumpResult((LockOrUnlockPumpAck)message);
  563. DebugLog($"LockOrUnlock ack, Source: {message.SourceAddress}, Destination: {message.DestinationAddress}, " +
  564. $"Operation: {((LockOrUnlockPumpAck)message).OperationType}, Result state: {((LockOrUnlockPumpAck)message).DispenserState}");
  565. }
  566. else if (message is DataDownloadRequest)
  567. {
  568. terminalManager.HandleDataDownloadRequest((DataDownloadRequest)message);
  569. }
  570. else if (message is DataContentRequest)
  571. {
  572. terminalManager.HandleDataContentRequest((DataContentRequest)message);
  573. }
  574. else if (message is ChangeAuthModeAck)
  575. {
  576. terminalManager.HandleModeChangeResult((ChangeAuthModeAck)message);
  577. }
  578. else if (message is CancelAuthRequest)
  579. {
  580. terminalManager.HandleCancelAuth((CancelAuthRequest)message);
  581. }
  582. else if (message is QueryGrayRecordRequest)
  583. {
  584. terminalManager.HandleQueryGrayRecord((QueryGrayRecordRequest)message);
  585. }
  586. else if (message is null)
  587. {
  588. terminalManager.HandleFakeNullMessage();
  589. }
  590. }
  591. private void SetupTerminalManagers()
  592. {
  593. foreach (var ptHandler in paymentTerminalHandlers)
  594. {
  595. foreach (var pumpId in ptHandler.AssociatedPumpIds)
  596. {
  597. var mgr = new TerminalManager(this, pumpId, ptHandler.GetSubAddressForPump(pumpId), spsManager, ptHandler, interval, this.CardAppType);
  598. terminalManagers.Add(mgr);
  599. InfoLog($"Setting up TerminalManager, Pump Id: {mgr.PumpId}, Sub address: {mgr.SubAddress}");
  600. }
  601. }
  602. }
  603. private void InitPumpPriceDict()
  604. {
  605. foreach (var ptHandler in paymentTerminalHandlers)
  606. {
  607. foreach (var p in ptHandler.AssociatedPumpIds)
  608. {
  609. if (!pumpPriceDict.ContainsKey(p))
  610. {
  611. pumpPriceDict.Add(p, false);
  612. }
  613. }
  614. }
  615. }
  616. private void SetupTransactionSubmitters()
  617. {
  618. foreach (var ptHandler in paymentTerminalHandlers)
  619. {
  620. foreach (var p in ptHandler.AssociatedPumpIds)
  621. {
  622. if (submitters.Any(s => s.Id == p))
  623. continue;
  624. var s = new TrxSubmitter(p, cloudCredential);
  625. submitters.Add(s);
  626. InfoLog($"Setting up Transaction submitter, Pump Id: {p}");
  627. }
  628. }
  629. }
  630. private TrxSubmitter GetSubmitter(int pumpId)
  631. {
  632. return submitters.FirstOrDefault(s => s.Id == pumpId);
  633. }
  634. private void SetFuelProductMapping()
  635. {
  636. if (!string.IsNullOrEmpty(fuelProductMapping))
  637. {
  638. var sequence = fuelProductMapping.Split(';')
  639. .Select(s => s.Split(':'))
  640. .Select(a => new { Barcode = int.Parse(a[0]), FuelNo = int.Parse(a[1]) });
  641. foreach (var pair in sequence)
  642. {
  643. if (!fuelMappingDict.ContainsKey(pair.Barcode))
  644. {
  645. fuelMappingDict.Add(pair.Barcode, pair.FuelNo);
  646. }
  647. }
  648. }
  649. }
  650. private int GetFuelNo(int barcode)
  651. {
  652. if (fuelMappingDict.ContainsKey(barcode))
  653. return fuelMappingDict[barcode];
  654. return -1;
  655. }
  656. public int GetBarcode(int fuelNo)
  657. {
  658. return fuelMappingDict.FirstOrDefault(x => x.Value == fuelNo).Key;
  659. }
  660. private void InitFuelName()
  661. {
  662. fuelNameDict.Clear();
  663. var fuelNames = spsManager.GetFuelNames();
  664. foreach (var item in fuelNameDict)
  665. {
  666. int barcode = GetBarcode(Convert.ToInt32(item.Key));
  667. if (!fuelNameDict.ContainsKey(barcode))
  668. {
  669. fuelNameDict.Add(barcode, item.Value);
  670. }
  671. }
  672. }
  673. private void InitFuelPrices()
  674. {
  675. fuelCodePriceDict.Clear();
  676. var fuelPrices = spsManager.GetCurrentFuelPrices();
  677. foreach (var item in fuelPrices)
  678. {
  679. if (!fuelCodePriceDict.ContainsKey(item.Key))
  680. {
  681. fuelCodePriceDict.Add(item.Key, item.Value);
  682. }
  683. }
  684. }
  685. public string GetFuelName(int barcode)
  686. {
  687. string fuelName = string.Empty;
  688. fuelNameDict.TryGetValue(barcode, out fuelName);
  689. return fuelName;
  690. }
  691. private TerminalManager GetTerminalManager(int pumpId)
  692. {
  693. return terminalManagers.FirstOrDefault(t => t.PumpId == pumpId);
  694. }
  695. private TerminalManager GetTerminalManager(string identifier, int pumpId)
  696. {
  697. return terminalManagers.FirstOrDefault(t => t.Identifier == identifier && t.SubAddress == pumpId);
  698. }
  699. private void InfoLog(string info)
  700. {
  701. logger.Info(info);
  702. }
  703. private void DebugLog(string debugMsg)
  704. {
  705. logger.Debug(debugMsg);
  706. }
  707. #endregion
  708. #region Properties
  709. public FdcServerHostApp FdcServer
  710. {
  711. get { return fdcServerApp; }
  712. }
  713. public SpsDataCourierApp SpsDataCourier => spsDataCourierApp;
  714. public int PosId { get; private set; }
  715. public long CurrentShiftNo
  716. {
  717. get { return initalData.ShiftNo; }
  718. }
  719. public DiscountServiceClient DiscountServiceClient { get; }
  720. public Dictionary<string, uint> CurrentFuelPrices => fuelCodePriceDict;
  721. public StationInfo StationInfo { get; private set; }
  722. public int ReservedBalance => reservedBalance;
  723. #endregion
  724. }
  725. #region Config parameters
  726. public class PosAppContructorParameterV1
  727. {
  728. public int PosId { get; set; }
  729. public string Username { get; set; }
  730. public string Password { get; set; }
  731. public string AuthServiceBaseUrl { get; set; }
  732. public string TransactionServiceBaseUrl { get; set; }
  733. public string DiscountServiceBaseUrl { get; set; }
  734. public bool OnlineDiscount { get; set; }
  735. public int DiscountTimeout { get; set; }
  736. public string DeviceSN { get; set; }
  737. public string PromoCategories { get; set; }
  738. public List<FuelMappingV1> FuelMappingArr { get; set; }
  739. public int CardAppType { get; set; }
  740. public int ReservedBalance { get; set; }
  741. public int PumpMode { get; set; }
  742. public int Interval { get; set; }
  743. public SpsDbConnectionSetting ConnectionString { get; set; }
  744. public int StationNo { get; set; }
  745. public string StationName { get; set; }
  746. }
  747. public class FuelMappingV1
  748. {
  749. public int Barcode { get; set; }
  750. public string FuelNo { get; set; }
  751. }
  752. public class SpsDbConnectionSetting
  753. {
  754. public string Server { get; set; }
  755. public int Port { get; set; }
  756. public string Username { get; set; }
  757. public string Password { get; set; }
  758. }
  759. #endregion
  760. }