App_Membership.cs 45 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775
  1. using AutoMapper;
  2. using Edge.Core.UniversalApi;
  3. using Gateway.POS.Models;
  4. using Microsoft.EntityFrameworkCore;
  5. using Microsoft.Extensions.DependencyInjection;
  6. using Microsoft.Extensions.Logging;
  7. using System;
  8. using System.Collections.Generic;
  9. using System.Linq;
  10. using System.Text;
  11. using System.Threading.Tasks;
  12. namespace Gateway.POS
  13. {
  14. public partial class App
  15. {
  16. [UniversalApi(Description = "Provide ONE of the paramter to limit the data, and other conditions will be ignorned. Leave all condition null(except page row count and page index) will return data order by timestamp desc.")]
  17. public async Task<Gateway.POS.MembershipOperationResult> GetMembershipAccountsSummary(int? accountId,
  18. string accountName, string accountCompanyName, string accountPhoneNumber, string oneOfSubAccountHolderName, string tagName, int pageRowCount, int pageIndex)
  19. {
  20. var mapper = this.services.GetRequiredService<IMapper>();
  21. List<MembershipAccount> accounts = null; new List<MembershipAccount>();
  22. if (accountId.HasValue)
  23. {
  24. using (var db = new PosAppDbContext())
  25. {
  26. accounts = await db.MembershipAccounts
  27. .Include(acct => acct.SubAccounts).ThenInclude(sa => sa.ProfitAccounts)//.ThenInclude(pa => pa.ProfitRecharges)
  28. .Include(acct => acct.SubAccounts).ThenInclude(sa => sa.Identities)
  29. .Include(acct => acct.SubAccounts).ThenInclude(sa => sa.SubAccountTags).ThenInclude(at => at.Tag)
  30. .Include(acct => acct.AccountTags).ThenInclude(at => at.Tag)
  31. .Include(acct => acct.ProfitAccounts)//.ThenInclude(pa => pa.ProfitRecharges)
  32. .Where(acct => acct.Id == accountId.Value)
  33. .OrderByDescending(acct => acct.ServerSideCreatedTimestamp)
  34. .Skip(pageRowCount * pageIndex).Take(pageRowCount)
  35. .ToListAsync();
  36. }
  37. }
  38. else if (!string.IsNullOrEmpty(accountName))
  39. {
  40. using (var db = new PosAppDbContext())
  41. {
  42. accounts = await db.MembershipAccounts
  43. .Include(acct => acct.SubAccounts).ThenInclude(sa => sa.ProfitAccounts)
  44. .Include(acct => acct.SubAccounts).ThenInclude(sa => sa.Identities)
  45. .Include(acct => acct.SubAccounts).ThenInclude(sa => sa.SubAccountTags).ThenInclude(at => at.Tag)
  46. .Include(acct => acct.AccountTags).ThenInclude(at => at.Tag)
  47. .Include(acct => acct.ProfitAccounts)
  48. .Where(acct => acct.Name.Contains(accountName))
  49. .OrderByDescending(acct => acct.ServerSideCreatedTimestamp)
  50. .Skip(pageRowCount * pageIndex).Take(pageRowCount)
  51. .ToListAsync();
  52. }
  53. }
  54. else if (!string.IsNullOrEmpty(accountCompanyName))
  55. {
  56. using (var db = new PosAppDbContext())
  57. {
  58. accounts = await db.MembershipAccounts
  59. .Include(acct => acct.SubAccounts).ThenInclude(sa => sa.ProfitAccounts)
  60. .Include(acct => acct.SubAccounts).ThenInclude(sa => sa.Identities)
  61. .Include(acct => acct.SubAccounts).ThenInclude(sa => sa.SubAccountTags).ThenInclude(at => at.Tag)
  62. .Include(acct => acct.AccountTags).ThenInclude(at => at.Tag)
  63. .Include(acct => acct.ProfitAccounts)
  64. .Where(acct => acct.CompanyName.Contains(accountCompanyName))
  65. .OrderByDescending(acct => acct.ServerSideCreatedTimestamp)
  66. .Skip(pageRowCount * pageIndex).Take(pageRowCount)
  67. .ToListAsync();
  68. }
  69. }
  70. else if (!string.IsNullOrEmpty(accountPhoneNumber))
  71. {
  72. using (var db = new PosAppDbContext())
  73. {
  74. accounts = await db.MembershipAccounts
  75. .Include(acct => acct.SubAccounts).ThenInclude(sa => sa.ProfitAccounts)
  76. .Include(acct => acct.SubAccounts).ThenInclude(sa => sa.Identities)
  77. .Include(acct => acct.SubAccounts).ThenInclude(sa => sa.SubAccountTags).ThenInclude(at => at.Tag)
  78. .Include(acct => acct.AccountTags).ThenInclude(at => at.Tag)
  79. .Include(acct => acct.ProfitAccounts)
  80. .Where(acct => acct.PhoneNumber.Contains(accountPhoneNumber))
  81. .OrderByDescending(acct => acct.ServerSideCreatedTimestamp)
  82. .Skip(pageRowCount * pageIndex).Take(pageRowCount)
  83. .ToListAsync();
  84. }
  85. }
  86. else if (!string.IsNullOrEmpty(oneOfSubAccountHolderName))
  87. {
  88. using (var db = new PosAppDbContext())
  89. {
  90. accounts = await db.MembershipSubAccounts
  91. .Include(sa => sa.Account).ThenInclude(acct => acct.ProfitAccounts)
  92. .Include(sa => sa.Account).ThenInclude(acct => acct.AccountTags).ThenInclude(at => at.Tag)
  93. .Include(sa => sa.Identities)
  94. .Include(sa => sa.SubAccountTags).ThenInclude(at => at.Tag)
  95. .Include(sa => sa.ProfitAccounts)
  96. .Where(sa => sa.SubAccountHolderName.Contains(oneOfSubAccountHolderName))
  97. .Select(sa => sa.Account)
  98. .OrderByDescending(acct => acct.ServerSideCreatedTimestamp)
  99. .Skip(pageRowCount * pageIndex).Take(pageRowCount)
  100. .ToListAsync();
  101. }
  102. }
  103. else if (!string.IsNullOrEmpty(tagName))
  104. {
  105. using (var db = new PosAppDbContext())
  106. {
  107. accounts = await db.MembershipAccounts
  108. .Include(acct => acct.SubAccounts).ThenInclude(sa => sa.ProfitAccounts)
  109. .Include(acct => acct.SubAccounts).ThenInclude(sa => sa.Identities)
  110. .Include(acct => acct.AccountTags).ThenInclude(at => at.Tag)
  111. .Include(acct => acct.ProfitAccounts).Where(acct => acct.AccountTags.Any(at => at.Tag.Name.Contains(tagName)))
  112. .OrderByDescending(acct => acct.ServerSideCreatedTimestamp)
  113. .Skip(pageRowCount * pageIndex).Take(pageRowCount)
  114. .ToListAsync();
  115. }
  116. }
  117. if (accounts == null)
  118. {
  119. using (var db = new PosAppDbContext())
  120. {
  121. accounts = await db.MembershipAccounts
  122. .Include(acct => acct.SubAccounts).ThenInclude(sa => sa.ProfitAccounts)//.ThenInclude(pa => pa.ProfitRecharges)
  123. .Include(acct => acct.SubAccounts).ThenInclude(sa => sa.Identities)
  124. .Include(acct => acct.SubAccounts).ThenInclude(sa => sa.SubAccountTags).ThenInclude(at => at.Tag)
  125. .Include(acct => acct.AccountTags).ThenInclude(at => at.Tag)
  126. .Include(acct => acct.ProfitAccounts)//.ThenInclude(pa => pa.ProfitRecharges)
  127. .OrderByDescending(acct => acct.ServerSideCreatedTimestamp)
  128. .Skip(pageRowCount * pageIndex).Take(pageRowCount)
  129. .ToListAsync();
  130. }
  131. }
  132. var r = new MembershipOperationResult(200, "", mapper.Map<List<MembershipAccountOutputDto>>(accounts));
  133. return r;
  134. }
  135. [UniversalApi(Description = "")]
  136. public async Task<Gateway.POS.MembershipOperationResult> GetMembershipSubAccountDetail(int subAccountId, int pageRowCount, int pageIndex)
  137. {
  138. var mapper = this.services.GetRequiredService<IMapper>();
  139. using (var db = new PosAppDbContext())
  140. {
  141. var subAccount = await db.MembershipSubAccounts
  142. .Include(sa => sa.Account).ThenInclude(acct => acct.ProfitAccounts)//.ThenInclude(pa => pa.ProfitRedeems)
  143. .Include(sa => sa.Account).ThenInclude(acct => acct.AccountTags).ThenInclude(at => at.Tag)
  144. .Include(sa => sa.ProfitAccounts)//.ThenInclude(pa => pa.ProfitRecharges)
  145. .Include(sa => sa.SubAccountTags).ThenInclude(at => at.Tag)
  146. .FirstOrDefaultAsync(sa => sa.Id == subAccountId);
  147. if (subAccount == null)
  148. return new MembershipOperationResult(404, "", null);
  149. if (subAccount.ProfitAccounts?.Any() ?? false)
  150. {
  151. foreach (var pa in subAccount.ProfitAccounts)
  152. {
  153. var latestRecharges = await db.MembershipProfitAccountRecharges.Include(rc => rc.ProfitAccount)
  154. .Where(rc => rc.ProfitAccount.Id == pa.Id)
  155. .OrderByDescending(rc => rc.Timestamp)
  156. .Skip(pageRowCount * pageIndex).Take(pageRowCount).ToListAsync();
  157. var latestRedeems = await db.MembershipProfitAccountRedeems.Include(rd => rd.ProfitAccount)
  158. .Where(rd => rd.ProfitAccount.Id == pa.Id)
  159. .OrderByDescending(rc => rc.Timestamp)
  160. .Skip(pageRowCount * pageIndex).Take(pageRowCount).ToListAsync();
  161. pa.ProfitRecharges = latestRecharges;
  162. pa.ProfitRedeems = latestRedeems;
  163. }
  164. }
  165. if (subAccount.Account.AllowSubAccountAccessProfitAccounts)
  166. {
  167. if (subAccount.Account.ProfitAccounts?.Any() ?? false)
  168. {
  169. foreach (var pa in subAccount.Account.ProfitAccounts)
  170. {
  171. var latestRecharges = await db.MembershipProfitAccountRecharges.Include(rc => rc.ProfitAccount)
  172. .Where(rc => rc.ProfitAccount.Id == pa.Id)
  173. .OrderByDescending(rc => rc.Timestamp)
  174. .Skip(pageRowCount * pageIndex).Take(pageRowCount).ToListAsync();
  175. var latestRedeems = await db.MembershipProfitAccountRedeems.Include(rd => rd.ProfitAccount)
  176. .Where(rd => rd.ProfitAccount.Id == pa.Id)
  177. .OrderByDescending(rc => rc.Timestamp)
  178. .Skip(pageRowCount * pageIndex).Take(pageRowCount).ToListAsync();
  179. pa.ProfitRecharges = latestRecharges;
  180. pa.ProfitRedeems = latestRedeems;
  181. }
  182. }
  183. }
  184. else
  185. {
  186. subAccount.Account.ProfitAccounts = null;
  187. }
  188. return new MembershipOperationResult(200, nameof(MembershipAccount), mapper.Map<MembershipAccountOutputDto>(subAccount.Account));
  189. }
  190. }
  191. [UniversalApi(Description = "")]
  192. public async Task<Gateway.POS.MembershipOperationResult> GetMembershipAccountDetail(int accountId, int pageRowCount, int pageIndex)
  193. {
  194. var mapper = this.services.GetRequiredService<IMapper>();
  195. using (var db = new PosAppDbContext())
  196. {
  197. var account = await db.MembershipAccounts
  198. .Include(acct => acct.SubAccounts).ThenInclude(subAcct => subAcct.ProfitAccounts)//.ThenInclude(pa => pa.ProfitRedeems)
  199. .Include(acct => acct.SubAccounts).ThenInclude(subAcct => subAcct.SubAccountTags).ThenInclude(at => at.Tag)
  200. .Include(acct => acct.ProfitAccounts)//.ThenInclude(pa => pa.ProfitRecharges)
  201. .Include(acct => acct.AccountTags).ThenInclude(at => at.Tag)
  202. .FirstOrDefaultAsync(acct => acct.Id == accountId);
  203. if (account == null)
  204. return new MembershipOperationResult(404, "", null);
  205. if (account.ProfitAccounts?.Any() ?? false)
  206. {
  207. foreach (var pa in account.ProfitAccounts)
  208. {
  209. var latestRecharges = await db.MembershipProfitAccountRecharges.Include(rc => rc.ProfitAccount)
  210. .Where(rc => rc.ProfitAccount.Id == pa.Id)
  211. .OrderByDescending(rc => rc.Timestamp)
  212. .Skip(pageRowCount * pageIndex).Take(pageRowCount).ToListAsync();
  213. var latestRedeems = await db.MembershipProfitAccountRedeems.Include(rd => rd.ProfitAccount)
  214. .Where(rd => rd.ProfitAccount.Id == pa.Id)
  215. .OrderByDescending(rc => rc.Timestamp)
  216. .Skip(pageRowCount * pageIndex).Take(pageRowCount).ToListAsync();
  217. pa.ProfitRecharges = latestRecharges;
  218. pa.ProfitRedeems = latestRedeems;
  219. }
  220. }
  221. return new MembershipOperationResult(200, nameof(MembershipAccount), mapper.Map<MembershipAccountOutputDto>(account));
  222. }
  223. }
  224. [Edge.Core.UniversalApi.UniversalApi(Description = "Get all membership tags, use parameter tagName to limit the scope, or leavel empty or null to return all.")]
  225. public async Task<Gateway.POS.MembershipOperationResult> GetMembershipTags(string tagName)
  226. {
  227. var mapper = this.services.GetRequiredService<IMapper>();
  228. List<MembershipTag> data;
  229. if (string.IsNullOrEmpty(tagName))
  230. {
  231. using (var db = new PosAppDbContext())
  232. {
  233. data = await db.MembershipTags.ToListAsync();
  234. }
  235. }
  236. else
  237. {
  238. using (var db = new PosAppDbContext())
  239. {
  240. data = await db.MembershipTags.ToListAsync();
  241. }
  242. }
  243. return new MembershipOperationResult(200, "", mapper.Map<List<MembershipTagDto>>(data));
  244. }
  245. [Edge.Core.UniversalApi.UniversalApi]
  246. public async Task<Gateway.POS.MembershipOperationResult> UpsertMembershipTag(Gateway.POS.Models.MembershipTagDto input)
  247. {
  248. var mapper = this.services.GetRequiredService<IMapper>();
  249. var tag = mapper.Map<MembershipTag>(input);
  250. if (!input.Id.HasValue)
  251. using (var db = new PosAppDbContext())
  252. {
  253. db.MembershipTags.Add(tag);
  254. var saveResult = await db.SaveChangesAsync();
  255. if (saveResult < 1)
  256. return new MembershipOperationResult(500, "The effected row count is < 1 when saving to db.");
  257. else
  258. return new MembershipOperationResult(200, "", mapper.Map<MembershipTag>(tag));
  259. }
  260. else if (!input.MarkForDeletion)
  261. {
  262. using (var db = new PosAppDbContext())
  263. {
  264. db.MembershipTags.Update(tag);
  265. var saveResult = await db.SaveChangesAsync();
  266. if (saveResult < 1)
  267. return new MembershipOperationResult(500, "The effected row count is < 1 when updating to db.");
  268. else
  269. return new MembershipOperationResult(200, "", mapper.Map<MembershipTag>(tag));
  270. }
  271. }
  272. else
  273. {
  274. using (var db = new PosAppDbContext())
  275. {
  276. db.MembershipTags.Remove(tag);
  277. var saveResult = await db.SaveChangesAsync();
  278. if (saveResult < 1)
  279. return new MembershipOperationResult(500, "The effected row count is < 1 when removing to db.");
  280. else
  281. return new MembershipOperationResult(200, "", mapper.Map<MembershipTag>(tag));
  282. }
  283. }
  284. }
  285. [Edge.Core.UniversalApi.UniversalApi]
  286. public async Task<Gateway.POS.MembershipOperationResult> UpsertMembershipAccount(Gateway.POS.Models.MembershipAccountInputDto input)
  287. {
  288. var mapper = this.services.GetRequiredService<IMapper>();
  289. //if (input.AccountTags != null)
  290. //{
  291. // input.AccountTags.RemoveAll(at => !at.MembershipTagId.HasValue);
  292. //}
  293. if (!input.Id.HasValue)
  294. {
  295. var newAcct = mapper.Map<MembershipAccount>(input);
  296. newAcct.ServerSideCreatedTimestamp = System.DateTime.Now;
  297. using (var db = new PosAppDbContext())
  298. {
  299. db.MembershipAccounts.Add(newAcct);
  300. if (newAcct.AccountTags != null)
  301. foreach (var t in newAcct.AccountTags)
  302. {
  303. t.Account = newAcct;
  304. if (t.MembershipTagId > 0)
  305. //dis-allow udpate tag here for make this api slim, should call standard alone api to do this.
  306. db.Entry(t.Tag).State = EntityState.Unchanged;
  307. else if (t.MembershipTagId <= 0)
  308. db.Entry(t.Tag).State = EntityState.Added;
  309. }
  310. var saveResult = await db.SaveChangesAsync();
  311. if (saveResult < 1)
  312. return new MembershipOperationResult(500, "The effected row count is < 1 when saving to db.");
  313. else
  314. return new MembershipOperationResult(200, "", mapper.Map<MembershipAccountOutputDto>(newAcct));
  315. }
  316. }
  317. else
  318. {
  319. var updateAcct = mapper.Map<MembershipAccount>(input);
  320. using (var db = new PosAppDbContext())
  321. {
  322. db.MembershipAccounts.Update(updateAcct);
  323. if (updateAcct.AccountTags != null)
  324. foreach (var t in updateAcct.AccountTags)
  325. {
  326. t.Account = updateAcct;
  327. if (t.MembershipTagId > 0)
  328. //dis-allow udpate tag here for make this api slim, should call standard alone api to do this.
  329. db.Entry(t.Tag).State = EntityState.Unchanged;
  330. else if (t.MembershipTagId <= 0)
  331. db.Entry(t.Tag).State = EntityState.Added;
  332. }
  333. db.Entry(updateAcct.SubAccounts).State = EntityState.Unchanged;
  334. db.Entry(updateAcct.ProfitAccounts).State = EntityState.Unchanged;
  335. var saveResult = await db.SaveChangesAsync();
  336. if (saveResult < 1)
  337. return new MembershipOperationResult(500, "The effected row count is < 1 when updating to db.");
  338. else
  339. return new MembershipOperationResult(200, "", mapper.Map<MembershipAccountOutputDto>(updateAcct));
  340. }
  341. }
  342. }
  343. [Edge.Core.UniversalApi.UniversalApi]
  344. public async Task<Gateway.POS.MembershipOperationResult> UpsertMembershipProfitAccount(Gateway.POS.Models.MembershipProfitAccountInputDto input)
  345. {
  346. if (!input.MembershipAccountId.HasValue && !input.MembershipSubAccountId.HasValue)
  347. return new MembershipOperationResult(400, "A Profit account must belongs to an account or subAccount, please provide at least one id.");
  348. var mapper = this.services.GetRequiredService<IMapper>();
  349. if (!input.Id.HasValue)
  350. {
  351. var newProfitAcct = mapper.Map<Gateway.POS.Models.MembershipProfitAccount>(input);
  352. newProfitAcct.ServerSideCreatedTimestamp = DateTime.Now;
  353. using (var db = new PosAppDbContext())
  354. {
  355. db.MembershipProfitAccounts.Add(newProfitAcct);
  356. var saveResult = await db.SaveChangesAsync();
  357. if (saveResult < 1)
  358. return new MembershipOperationResult(500, "The effected row count is < 1 when saving to db.");
  359. else
  360. return new MembershipOperationResult(200, "", newProfitAcct);
  361. }
  362. }
  363. else
  364. {
  365. var updateProfitAcct = mapper.Map<Gateway.POS.Models.MembershipProfitAccount>(input);
  366. using (var db = new PosAppDbContext())
  367. {
  368. db.MembershipProfitAccounts.Update(updateProfitAcct);
  369. db.Entry(updateProfitAcct.ProfitRecharges).State = EntityState.Unchanged;
  370. db.Entry(updateProfitAcct.ProfitRedeems).State = EntityState.Unchanged;
  371. var saveResult = await db.SaveChangesAsync();
  372. if (saveResult < 1)
  373. return new MembershipOperationResult(500, "The effected row count is < 1 when updating to db.");
  374. else
  375. return new MembershipOperationResult(200, "", updateProfitAcct);
  376. }
  377. }
  378. }
  379. [Edge.Core.UniversalApi.UniversalApi(Description = "Update or insert a sub account to an account.")]
  380. public async Task<Gateway.POS.MembershipOperationResult> UpsertMembershipSubAccount(
  381. string cardReaderNameIfIssueCard, Gateway.POS.Models.MembershipSubAccountInputDto input)
  382. {
  383. if (input.AccountId == 0)
  384. return new Gateway.POS.MembershipOperationResult(400, $"Must specify a valid AccountId");
  385. using (var db = new PosAppDbContext())
  386. {
  387. var foundAcct = await db.MembershipAccounts.FirstOrDefaultAsync(acct => acct.Id == input.AccountId);
  388. if (foundAcct == null)
  389. return new Gateway.POS.MembershipOperationResult(400, $"Could not find the correlated account with accountId: {input.AccountId}");
  390. }
  391. var mapper = this.services.GetRequiredService<IMapper>();
  392. if (!input.Id.HasValue || input.Id.Value == 0)
  393. {
  394. var newSubAcct = mapper.Map<MembershipSubAccount>(input);
  395. newSubAcct.ServerSideCreatedTimestamp = System.DateTime.Now;
  396. if (newSubAcct.Identities?.Any(i => i.IdentityCarrierType == MembershipSubAccountIdentityCarrierTypeEnum.Card_DesFireEv1) ?? false)
  397. {
  398. var potentialSubAccountIdentity = "";
  399. if (cardReaderNameIfIssueCard == "fakereader")
  400. {
  401. potentialSubAccountIdentity = string.Concat("fake_", System.DateTime.Now.Ticks.ToString());
  402. }
  403. else
  404. {
  405. var targetDesFireEv1CardModule = this.classicCpuCardApp?.CardReaderModuels?.FirstOrDefault(m => m.Name == cardReaderNameIfIssueCard);
  406. if (targetDesFireEv1CardModule == null)
  407. return new Gateway.POS.MembershipOperationResult(404, $"找不到读卡器, 它的名称是: {cardReaderNameIfIssueCard}", null);
  408. var readCardIdResults = await this.classicCpuCardApp.ReadCardID(cardReaderNameIfIssueCard);
  409. var readCardIdSucceedResult = readCardIdResults.FirstOrDefault(r => r.OverallResultCode == 200);
  410. if (readCardIdSucceedResult == null)
  411. return new Gateway.POS.MembershipOperationResult(404, $"从读卡器: {cardReaderNameIfIssueCard} 上读取卡片的UID失败, 请确保将卡片靠近读卡器", null);
  412. potentialSubAccountIdentity = readCardIdSucceedResult.PrettyHexStrData.Replace(" ", "");
  413. var readCardContentResults = await this.classicCpuCardApp.ReadCardContent(cardReaderNameIfIssueCard, 1, 1);
  414. var readCardContentSucceedResult = readCardContentResults.FirstOrDefault(r => r.OverallResultCode == 200);
  415. if (readCardContentSucceedResult == null)
  416. return new Gateway.POS.MembershipOperationResult(404, $"从读卡器: {cardReaderNameIfIssueCard} 上读取卡片的内容失败", readCardContentResults);
  417. /*read a card open id, and the content can be read as well indicates this is a card from our system, otherwise, could be a card just faked the open id*/
  418. using (var db = new PosAppDbContext())
  419. {
  420. var foundSubAcct = await db.MembershipSubAccounts.Include(sa => sa.Account).Include(sa => sa.Identities)
  421. .FirstOrDefaultAsync(sa =>
  422. sa.Identities.Any(i => i.IdentityCarrierType == MembershipSubAccountIdentityCarrierTypeEnum.Card_DesFireEv1
  423. && i.Identity == potentialSubAccountIdentity));
  424. if (foundSubAcct != null)
  425. return new Gateway.POS.MembershipOperationResult(409, $"当前卡片已经分配给子帐户: {foundSubAcct.SubAccountHolderName ?? ""} (其属于帐户: {foundSubAcct.Account?.Name ?? ""}), 请换一张新卡片或者将当前卡片与已经绑定的子帐户解除绑定, 然后再试");
  426. }
  427. var issueCardWriteContent = string.Format("{0}|{1}|{2}", this.siteId, newSubAcct.Account.Id, potentialSubAccountIdentity);
  428. var writeCardContentResults = await this.classicCpuCardApp.WriteCardWithTextData(cardReaderNameIfIssueCard, 1, 1, issueCardWriteContent);
  429. var writeCardContentSucceedResult = writeCardContentResults.FirstOrDefault(r => r.OverallResultCode == 200);
  430. if (writeCardContentSucceedResult == null)
  431. return new Gateway.POS.MembershipOperationResult(500, $"在通过读卡器: {cardReaderNameIfIssueCard} 向卡片中写入内容时失败", writeCardContentResults);
  432. }
  433. newSubAcct.Identities.First(i => i.IdentityCarrierType == MembershipSubAccountIdentityCarrierTypeEnum.Card_DesFireEv1).Identity = potentialSubAccountIdentity;
  434. }
  435. using (var db = new PosAppDbContext())
  436. {
  437. newSubAcct.Account.Id = input.AccountId;
  438. db.MembershipSubAccounts.Add(newSubAcct);
  439. if (newSubAcct.SubAccountTags != null)
  440. foreach (var t in newSubAcct.SubAccountTags)
  441. {
  442. t.SubAccount = newSubAcct;
  443. if (t.MembershipTagId > 0)
  444. //dis-allow udpate tag here for make this api slim, should call standard alone api to do this.
  445. db.Entry(t.Tag).State = EntityState.Unchanged;
  446. else if (t.MembershipTagId <= 0)
  447. db.Entry(t.Tag).State = EntityState.Added;
  448. }
  449. //dis-allow udpate Account
  450. db.Entry(newSubAcct.Account).State = EntityState.Unchanged;
  451. try
  452. {
  453. var saveResult = await db.SaveChangesAsync();
  454. if (saveResult < 1)
  455. return new MembershipOperationResult(500, "UpsertMembershipSubAccount(Insert), The effected row count is < 1 when saving to db.");
  456. else
  457. return new MembershipOperationResult(200, "", mapper.Map<MembershipSubAccountOutputDto>(newSubAcct));
  458. }
  459. catch (Exception eee)
  460. {
  461. return new MembershipOperationResult(500, $"UpsertMembershipSubAccount(Insert), Saving db exceptioned: {eee}");
  462. }
  463. }
  464. }
  465. else
  466. {
  467. var updateSubAcct = mapper.Map<Gateway.POS.Models.MembershipSubAccount>(input);
  468. updateSubAcct.ServerSideLastModifiedTimestamp = System.DateTime.Now;
  469. using (var db = new PosAppDbContext())
  470. {
  471. db.MembershipSubAccounts.Update(updateSubAcct);
  472. if (updateSubAcct.SubAccountTags != null)
  473. foreach (var t in updateSubAcct.SubAccountTags)
  474. {
  475. t.SubAccount = updateSubAcct;
  476. if (t.MembershipTagId > 0)
  477. //dis-allow udpate tag here for make this api slim, should call standard alone api to do this.
  478. db.Entry(t.Tag).State = EntityState.Unchanged;
  479. else if (t.MembershipTagId <= 0)
  480. db.Entry(t.Tag).State = EntityState.Added;
  481. }
  482. db.Entry(updateSubAcct.Account).State = EntityState.Unchanged;
  483. db.Entry(updateSubAcct).Collection(p => p.ProfitAccounts).IsModified = false;
  484. db.Entry(updateSubAcct).Collection(p => p.Identities).IsModified = false;
  485. try
  486. {
  487. var saveResult = await db.SaveChangesAsync();
  488. if (saveResult < 1)
  489. return new MembershipOperationResult(500, "UpsertMembershipSubAccount(Update), The effected row count is < 1 when updating to db.");
  490. else
  491. return new MembershipOperationResult(200, "", mapper.Map<MembershipSubAccountOutputDto>(updateSubAcct));
  492. }
  493. catch (Exception eee)
  494. {
  495. return new MembershipOperationResult(500, $"UpsertMembershipSubAccount(Update), Saving db exceptioned: {eee}");
  496. }
  497. }
  498. }
  499. }
  500. internal async Task<MembershipOperationResult> InternalRechargeMembershipAccount(int membershipAccountId,
  501. MembershipProfitAccountProfitTypeEnum rechargeProfitType, MembershipProfitAccountRechargeRequest input, PosAppDbContext unitOfWorkDb)
  502. {
  503. var mapper = this.services.GetRequiredService<IMapper>();
  504. var foundAcct = await unitOfWorkDb.MembershipAccounts.Include(acct => acct.ProfitAccounts).FirstOrDefaultAsync(acct => acct.Id == membershipAccountId);
  505. if (foundAcct == null)
  506. {
  507. return new MembershipOperationResult(404, $"Could not find membership account with id: {membershipAccountId}", null);
  508. }
  509. var foundProfitAccount = foundAcct.ProfitAccounts.FirstOrDefault(pa => pa.ProfitType == rechargeProfitType);
  510. if (foundProfitAccount == null)
  511. {
  512. /*create a profit account and then recharge it*/
  513. var newProfitAcct = new MembershipProfitAccount();
  514. newProfitAcct.MembershipAccountId = membershipAccountId;
  515. newProfitAcct.ProfitType = rechargeProfitType;
  516. newProfitAcct.Balance = input.RechargeAmount;
  517. unitOfWorkDb.MembershipProfitAccounts.Add(newProfitAcct);
  518. foundProfitAccount = newProfitAcct;
  519. }
  520. else
  521. {
  522. foundProfitAccount.Balance = (foundProfitAccount.Balance ?? 0) + input.RechargeAmount;
  523. unitOfWorkDb.MembershipProfitAccounts.Update(foundProfitAccount);
  524. }
  525. var recharge = new MembershipProfitAccountRecharge();
  526. recharge.RechargeAmount = input.RechargeAmount;
  527. recharge.SourceTrx = input.SourceTrx;
  528. recharge.Timestamp = DateTime.Now;
  529. recharge.ProfitAccount = foundProfitAccount;
  530. recharge.ProfitAccountAmountBalance = foundProfitAccount.Balance;
  531. unitOfWorkDb.MembershipProfitAccountRecharges.Add(recharge);
  532. return new MembershipOperationResult(200, "", recharge);
  533. }
  534. internal async Task<MembershipOperationResult> InternalRechargeMembershipSubAccount(int membershipSubAccountId,
  535. MembershipProfitAccountProfitTypeEnum rechargeProfitType, MembershipProfitAccountRechargeRequest input, PosAppDbContext unitOfWorkDb)
  536. {
  537. var mapper = this.services.GetRequiredService<IMapper>();
  538. var foundSubAcct = await unitOfWorkDb.MembershipSubAccounts.Include(acct => acct.ProfitAccounts).FirstOrDefaultAsync(subAcct => subAcct.Id == membershipSubAccountId);
  539. if (foundSubAcct == null)
  540. {
  541. return new MembershipOperationResult(404, $"Could not find membership sub account with id: {membershipSubAccountId}", null);
  542. }
  543. var foundProfitAccount = foundSubAcct.ProfitAccounts.FirstOrDefault(pa => pa.ProfitType == rechargeProfitType);
  544. if (foundProfitAccount == null)
  545. {
  546. /*create a profit account and then recharge it*/
  547. var newProfitAcct = new MembershipProfitAccount();
  548. newProfitAcct.MembershipSubAccountId = membershipSubAccountId;
  549. newProfitAcct.ProfitType = rechargeProfitType;
  550. newProfitAcct.Balance = input.RechargeAmount;
  551. unitOfWorkDb.MembershipProfitAccounts.Add(newProfitAcct);
  552. foundProfitAccount = newProfitAcct;
  553. }
  554. else
  555. {
  556. foundProfitAccount.Balance = (foundProfitAccount.Balance ?? 0) + input.RechargeAmount;
  557. unitOfWorkDb.MembershipProfitAccounts.Update(foundProfitAccount);
  558. }
  559. var recharge = new MembershipProfitAccountRecharge();
  560. recharge.CreateByOperatorId = input.CreateByOperatorId;
  561. recharge.RechargeAmount = input.RechargeAmount;
  562. recharge.SourceTrx = input.SourceTrx;
  563. recharge.Timestamp = DateTime.Now;
  564. recharge.ProfitAccount = foundProfitAccount;
  565. recharge.ProfitAccountAmountBalance = foundProfitAccount.Balance;
  566. unitOfWorkDb.MembershipProfitAccountRecharges.Add(recharge);
  567. return new MembershipOperationResult(200, "", recharge);
  568. }
  569. internal async Task<MembershipOperationResult> InternalRechargeMembershipSubAccountByOpenClassicCpuCardReaderAndReadCard(string cardReaderName,
  570. MembershipProfitAccountProfitTypeEnum rechargeProfitType, MembershipProfitAccountRechargeRequest input, PosAppDbContext unitOfWorkDb)
  571. {
  572. var mapper = this.services.GetRequiredService<IMapper>();
  573. var targetDesFireEv1CardModule = this.classicCpuCardApp?.CardReaderModuels?.FirstOrDefault(m => m.Name == cardReaderName);
  574. if (targetDesFireEv1CardModule == null)
  575. return new Gateway.POS.MembershipOperationResult(500, $"找不到读卡器, 它的名称是: {cardReaderName}", null);
  576. var readCardIdResults = await this.classicCpuCardApp.ReadCardID(cardReaderName);
  577. var readCardIdSucceedResult = readCardIdResults.FirstOrDefault(r => r.OverallResultCode == 200);
  578. if (readCardIdSucceedResult == null)
  579. return new Gateway.POS.MembershipOperationResult(500, $"Could not read any Card(UID) on card reader: {cardReaderName}, make sure the card is placed near the reader", null);
  580. /*read a card open id, and the content can be read as well indicates this is a card from our system, otherwise, could be a card just faked the open id*/
  581. var readCardContentResults = await this.classicCpuCardApp.ReadCardContent(cardReaderName, 1, 1);
  582. var readCardContentSucceedResult = readCardContentResults.FirstOrDefault(r => r.OverallResultCode == 200);
  583. if (readCardContentSucceedResult == null)
  584. return new Gateway.POS.MembershipOperationResult(500, $"Could not read Card content on card reader: {cardReaderName}", readCardContentResults);
  585. var potentialSubAccountIdentity = readCardIdSucceedResult.PrettyHexStrData.Replace(" ", "");
  586. var foundSubAcct =
  587. await unitOfWorkDb.MembershipSubAccounts
  588. //.Include(sa => sa.ProfitAccounts)
  589. //.Include(sa => sa.Account).ThenInclude(acct => acct.ProfitAccounts)
  590. .Include(sa => sa.Identities)
  591. .FirstOrDefaultAsync(sa => sa.Identities.Any(i => i.IdentityCarrierType == MembershipSubAccountIdentityCarrierTypeEnum.Card_DesFireEv1 && i.Identity == potentialSubAccountIdentity));
  592. if (foundSubAcct == null)
  593. return new Gateway.POS.MembershipOperationResult(404, string.Concat("Could not find sub Account with identity: ", potentialSubAccountIdentity), null);
  594. var rechargeResult = await this.InternalRechargeMembershipSubAccount(foundSubAcct.Id, rechargeProfitType, input, unitOfWorkDb);
  595. return rechargeResult;
  596. }
  597. internal async Task<Gateway.POS.MembershipOperationResult> InternalRedeemMembershipProfit(MembershipProfitAccountRedeemRequest input,
  598. PosAppDbContext unitOfWorkDb)
  599. {
  600. var mapper = this.services.GetRequiredService<IMapper>();
  601. var foundSubAcct =
  602. await unitOfWorkDb.MembershipSubAccounts
  603. //.Include(sa => sa.ProfitAccounts)
  604. //.Include(sa => sa.Account).ThenInclude(acct => acct.ProfitAccounts)
  605. .Include(sa => sa.Identities)
  606. .FirstOrDefaultAsync(sa => sa.Identities.Any(i => i.IdentityCarrierType == input.SubAccountIdentity.IdentityCarrierType && i.Identity == input.SubAccountIdentity.Identity));
  607. if (foundSubAcct == null)
  608. return new Gateway.POS.MembershipOperationResult(404, $"找不到拥有标识符为: {input.SubAccountIdentity.Identity} 的子帐户", null);
  609. var result = await this.InternalRedeemMembershipProfit(foundSubAcct.Id, input, unitOfWorkDb);
  610. return result;
  611. }
  612. internal async Task<Gateway.POS.MembershipOperationResult> InternalRedeemMembershipProfit(int membershipSubAccountId,
  613. MembershipProfitAccountRedeemRequest input, PosAppDbContext unitOfWorkDb)
  614. {
  615. var mapper = this.services.GetRequiredService<IMapper>();
  616. var foundSubAcct =
  617. await unitOfWorkDb.MembershipSubAccounts
  618. .Include(sa => sa.ProfitAccounts)
  619. .Include(sa => sa.Account).ThenInclude(acct => acct.ProfitAccounts)
  620. .Include(sa => sa.Identities)
  621. .FirstOrDefaultAsync(sa => sa.Id == membershipSubAccountId);
  622. if (foundSubAcct == null)
  623. return new Gateway.POS.MembershipOperationResult(404, $"找不到拥有标识符为: {input.SubAccountIdentity.Identity} 的子帐户", null);
  624. #region choose profit account, could be from subAcct or acct, subAcct has the priority.
  625. MembershipProfitAccount targetProfitAccount = null;
  626. var profitAccountOnSubAcct = foundSubAcct?.ProfitAccounts?.FirstOrDefault(pa => pa.ProfitType == input.ProfitAccountType);
  627. if (profitAccountOnSubAcct != null)
  628. targetProfitAccount = profitAccountOnSubAcct;
  629. else if (profitAccountOnSubAcct == null && foundSubAcct.Account.AllowSubAccountAccessProfitAccounts)
  630. {
  631. var profitAccountOnAcct = foundSubAcct.Account?.ProfitAccounts?.FirstOrDefault(pa => pa.ProfitType == input.ProfitAccountType);
  632. if (profitAccountOnAcct == null)
  633. return new MembershipOperationResult(404, $"子帐户: {foundSubAcct.SubAccountHolderName} (属于帐户: {foundSubAcct.Account.Name}) 上并未创建任何权益帐户");
  634. targetProfitAccount = profitAccountOnAcct;
  635. }
  636. else
  637. return new Gateway.POS.MembershipOperationResult(404, $"子帐户: {foundSubAcct.SubAccountHolderName} (id: {foundSubAcct.Id}) 上并未创建类型为: {input.ProfitAccountType} 的权益帐户(且当前子帐户不允许使用帐户上的权益帐户)", null);
  638. #endregion
  639. if ((targetProfitAccount.Balance ?? 0) < (input.RedeemProfitAmount ?? 0))
  640. {
  641. return new MembershipOperationResult(404, $"当前权益帐户(类型: {targetProfitAccount.ProfitType})中的余额: {targetProfitAccount.Balance ?? 0} 不足以支付当前请求");
  642. }
  643. else if (targetProfitAccount.RedeemAuthMode == MembershipProfitAccountRedeemAuthModeEnum.Password)
  644. {
  645. if (targetProfitAccount.RedeemAuthPassword != input.RedeemAuthCode)
  646. {
  647. return new Gateway.POS.MembershipOperationResult(403, $"当前权益帐户(类型: {targetProfitAccount.ProfitType})的密码输入错误");
  648. }
  649. }
  650. targetProfitAccount.Balance = (targetProfitAccount.Balance ?? 0) - (input.RedeemProfitAmount ?? 0);
  651. unitOfWorkDb.MembershipProfitAccounts.Update(targetProfitAccount);
  652. var redeem = new MembershipProfitAccountRedeem();
  653. redeem.SourceTrx = input.SourceTrx;
  654. redeem.Timestamp = DateTime.Now;
  655. redeem.ProfitAccount = targetProfitAccount;
  656. redeem.Purpose = input.Purpose;
  657. redeem.RedeemedProfitAmount = input.RedeemProfitAmount;
  658. redeem.RedeemedProfitComplexData = input.RedeemProfitComplexData;
  659. redeem.ProfitAccountAmountBalance = targetProfitAccount.Balance;
  660. redeem.CreateByOperatorId = input.CreateByOperatorId;
  661. redeem.Description = input.Description;
  662. unitOfWorkDb.MembershipProfitAccountRedeems.Add(redeem);
  663. return new MembershipOperationResult(200, "", redeem);
  664. }
  665. internal async Task<Gateway.POS.MembershipOperationResult> InternalRedeemMembershipProfitByOpenClassicCpuCardReaderAndReadCard(string cardReaderName,
  666. MembershipProfitAccountRedeemRequest input, PosAppDbContext unitOfWorkDb)
  667. {
  668. var mapper = this.services.GetRequiredService<IMapper>();
  669. var targetDesFireEv1CardModule = this.classicCpuCardApp?.CardReaderModuels?.FirstOrDefault(m => m.Name == cardReaderName);
  670. if (targetDesFireEv1CardModule == null)
  671. return new Gateway.POS.MembershipOperationResult(500, $"找不到读卡器, 它的名称是: {cardReaderName}", null);
  672. var readCardIdResults = await this.classicCpuCardApp.ReadCardID(cardReaderName);
  673. var readCardIdSucceedResult = readCardIdResults.FirstOrDefault(r => r.OverallResultCode == 200);
  674. if (readCardIdSucceedResult == null)
  675. return new Gateway.POS.MembershipOperationResult(500, $"从读卡器: {cardReaderName} 上读取卡片的UID失败, 请确保将卡片靠近读卡器", null);
  676. /*read a card open id, and the content can be read as well indicates this is a card from our system, otherwise, could be a card just faked the open id*/
  677. var readCardContentResults = await this.classicCpuCardApp.ReadCardContent(cardReaderName, 1, 1);
  678. var readCardContentSucceedResult = readCardContentResults.FirstOrDefault(r => r.OverallResultCode == 200);
  679. if (readCardContentSucceedResult == null)
  680. return new Gateway.POS.MembershipOperationResult(500, $"从读卡器: {cardReaderName} 上读取卡片的内容失败", readCardContentResults);
  681. var potentialSubAccountIdentity = readCardIdSucceedResult.PrettyHexStrData.Replace(" ", "");
  682. input.SubAccountIdentity = new MembershipSubAccountIdentityInputDto()
  683. {
  684. IdentityCarrierType = MembershipSubAccountIdentityCarrierTypeEnum.Card_DesFireEv1,
  685. Identity = potentialSubAccountIdentity
  686. };
  687. var redeemResult = await this.InternalRedeemMembershipProfit(input, unitOfWorkDb);
  688. return redeemResult;
  689. }
  690. [Edge.Core.UniversalApi.UniversalApi(Description = "Redeem profit from a SubAccount, and SubAccount's Identity will be read by a card reader, thus no need to fill it in input, or it'll get overwriten")]
  691. public Task<Gateway.POS.MembershipOperationResult> RedeemMembershipProfitByOpenClassicCpuCardReaderAndReadCard(string cardReaderName,
  692. MembershipProfitAccountRedeemRequest input)
  693. {
  694. return this.InternalRedeemMembershipProfitByOpenClassicCpuCardReaderAndReadCard(cardReaderName, input, new PosAppDbContext());
  695. }
  696. }
  697. public class MembershipOperationResult
  698. {
  699. public int OverallResultCode { get; set; }
  700. public string Message { get; }
  701. public object Data { get; set; }
  702. public ShengJu_CUT100_DES.GroupHandler.CardOperationResult[] CardOperationResults { get; set; }
  703. public MembershipOperationResult(int overallResultCode, string message, object data)
  704. {
  705. this.OverallResultCode = overallResultCode;
  706. this.Message = message;
  707. this.Data = data;
  708. }
  709. public MembershipOperationResult(int overallResultCode, string message)
  710. : this(overallResultCode, message, null)
  711. {
  712. }
  713. public MembershipOperationResult(int overallResultCode)
  714. : this(overallResultCode, null, null)
  715. {
  716. }
  717. }
  718. }