TmidController.cs 75 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258
  1. using Azure.Cosmos;
  2. using DocumentFormat.OpenXml.Office2010.Excel;
  3. using Microsoft.AspNetCore.Mvc;
  4. using Microsoft.Azure.Cosmos.Table;
  5. using Microsoft.Extensions.Configuration;
  6. using Microsoft.Extensions.Options;
  7. using System;
  8. using System.Collections.Generic;
  9. using System.Linq;
  10. using System.Reflection;
  11. using System.Text.Json;
  12. using System.Threading.Tasks;
  13. using TEAMModelOS.Models;
  14. using TEAMModelOS.SDK;
  15. using TEAMModelOS.SDK.DI;
  16. using TEAMModelOS.SDK.Extension;
  17. using TEAMModelOS.SDK.Models;
  18. using TEAMModelOS.SDK.Services;
  19. using static TEAMModelBI.Models.Extension.GeoRegion;
  20. namespace TEAMModelBI.Controllers.BITmid
  21. {
  22. [Route("tmid")]
  23. [ApiController]
  24. public class TmidController : ControllerBase
  25. {
  26. private readonly AzureCosmosFactory _azureCosmos;
  27. private readonly AzureStorageFactory _azureStorage;
  28. private readonly AzureRedisFactory _azureRedis;
  29. private readonly DingDing _dingDing;
  30. private readonly Option _option;
  31. private readonly IConfiguration _configuration;
  32. private readonly HttpTrigger _httpTrigger;
  33. public TmidController(AzureCosmosFactory azureCosmos, AzureStorageFactory azureStorage, AzureRedisFactory azureRedis, DingDing dingDing, IOptionsSnapshot<Option> option, IConfiguration configuration, HttpTrigger httpTrigger)
  34. {
  35. _azureCosmos = azureCosmos;
  36. _azureStorage = azureStorage;
  37. _azureRedis = azureRedis;
  38. _dingDing = dingDing;
  39. _option = option?.Value;
  40. _configuration = configuration;
  41. _httpTrigger = httpTrigger;
  42. }
  43. /// <summary>
  44. /// 取得TMID綜合資料
  45. /// </summary>
  46. /// <param name="jsonElement"></param>
  47. /// <returns></returns>
  48. [ProducesDefaultResponseType]
  49. //[AuthToken(Roles = "admin")]
  50. [HttpPost("get-tmidstics")]
  51. public async Task<IActionResult> GetTmidStics(JsonElement jsonElement)
  52. {
  53. try
  54. {
  55. if (!jsonElement.TryGetProperty("tmids", out JsonElement tmidsJobj)) return BadRequest();//TMID(array)
  56. var tmids = tmidsJobj.Deserialize<List<string>>();
  57. if (tmids.Count is 0) return BadRequest();
  58. var datetime = DateTimeOffset.UtcNow.AddDays(-1);
  59. int y = datetime.Year;
  60. int m = datetime.Month;
  61. int d = datetime.Day;
  62. string dateFromStr = (jsonElement.TryGetProperty("dateFrom", out JsonElement dateFromJobj)) ? dateFromJobj.GetString() : string.Empty; //查詢日期:起始(string)[例]2023-03-05
  63. string dateToStr = (jsonElement.TryGetProperty("dateTo", out JsonElement dateToJobj)) ? dateToJobj.GetString() : string.Empty; //查詢日期:結束(string)[例]2023-03-27
  64. string dateUnit = (jsonElement.TryGetProperty("dateUnit", out JsonElement dateUnitJobj)) ? dateUnitJobj.GetString().ToLower() : "month";
  65. string mode = (jsonElement.TryGetProperty("mode", out JsonElement modeJobj)) ? modeJobj.GetString().ToLower() : "default";
  66. //起始終止日期換算
  67. long dateTimeFromSec = 0;
  68. long dateTimeToSec = 0;
  69. if(!string.IsNullOrWhiteSpace(dateFromStr) && !string.IsNullOrWhiteSpace(dateToStr))
  70. {
  71. DateTimeOffset dateTimeFrom;
  72. DateTimeOffset dateTimeTo;
  73. List<string> dateFromList = dateFromStr.Split('-').ToList();
  74. int dateFromYear = Convert.ToInt32(dateFromList[0], 10);
  75. int dateFromMonth = (dateUnit.Equals("day") || dateUnit.Equals("month")) ? Convert.ToInt32(dateFromList[1], 10) : 1;
  76. int dateFromDay = (dateUnit.Equals("day")) ? Convert.ToInt32(dateFromList[2], 10) : 1;
  77. List<string> dateToList = dateToStr.Split('-').ToList();
  78. int dateToYear = Convert.ToInt32(dateToList[0], 10);
  79. int dateToMonth = (dateUnit.Equals("day") || dateUnit.Equals("month")) ? Convert.ToInt32(dateToList[1], 10) : 1;
  80. int dateToDay = (dateUnit.Equals("day")) ? Convert.ToInt32(dateToList[2], 10) : 1;
  81. switch (dateUnit)
  82. {
  83. case "year":
  84. dateTimeFrom = new DateTimeOffset(dateFromYear, 1, 1, 0, 0, 0, TimeSpan.Zero);
  85. dateTimeTo = new DateTimeOffset(dateToYear, 12, 31, 23, 59, 59, TimeSpan.Zero);
  86. break;
  87. case "month":
  88. dateTimeFrom = new DateTimeOffset(dateFromYear, dateFromMonth, 1, 0, 0, 0, TimeSpan.Zero);
  89. dateTimeTo = new DateTimeOffset(dateToYear, dateToMonth, DateTime.DaysInMonth(dateToYear, dateToMonth), 23, 59, 59, TimeSpan.Zero);
  90. break;
  91. case "day":
  92. dateTimeFrom = new DateTimeOffset(dateFromYear, dateFromMonth, dateFromDay, 0, 0, 0, TimeSpan.Zero);
  93. dateTimeTo = new DateTimeOffset(dateToYear, dateToMonth, dateToDay, 23, 59, 59, TimeSpan.Zero);
  94. break;
  95. default:
  96. dateTimeFrom = new DateTimeOffset(y, m, d, 0, 0, 0, TimeSpan.Zero);
  97. dateTimeTo = new DateTimeOffset(y, m, d, 23, 59, 59, TimeSpan.Zero);
  98. break;
  99. }
  100. dateTimeFromSec = dateTimeFrom.ToUnixTimeSeconds();
  101. dateTimeToSec = dateTimeTo.ToUnixTimeSeconds();
  102. y = 0;
  103. m = 0;
  104. d = 0;
  105. }
  106. //取得項目列表
  107. List<string> eventList = new List<string>();
  108. if(mode.Equals("simple"))
  109. {
  110. eventList = new List<string>() { "points" };
  111. } else
  112. {
  113. eventList = new List<string>() { "coupons", "login", "prod", "points", "iot", "ies5", "benefits", "sokrates" };
  114. }
  115. //服務Client端
  116. var cosmosClientIes5 = _azureCosmos.GetCosmosClient(); //CosmosDB IES5
  117. var cosmosClientCsv2 = _azureCosmos.GetCosmosClient(name: "CoreServiceV2"); //CosmosDB CSV2
  118. var storageClientCsv2 = _azureStorage.GetCloudTableClient(name: "CoreServiceV2"); //Storage CSV2
  119. var tableCouponClient = storageClientCsv2.GetTableReference("Coupon");
  120. var tablePointsClient = storageClientCsv2.GetTableReference("Points");
  121. var redisClient = _azureRedis.GetRedisClient(4);
  122. //取得TMID基本資料
  123. Dictionary<string, TmidStics> tmidDic = new();
  124. QueryDefinition query =
  125. new QueryDefinition(@"SELECT c.id, c.name, c.picture, c.mobile, c.mail, c.lang, c.wechat, c.facebook, c.google, c.ding, c.apple, c.educloudtw, c.ts FROM c WHERE (ARRAY_CONTAINS(@key, c.id) OR ARRAY_CONTAINS(@key, c.mobile))")
  126. .WithParameter("@key", tmids);
  127. await foreach (var item in cosmosClientCsv2
  128. .GetContainer("Core", "ID2")
  129. .GetItemQueryStreamIterator(query, requestOptions: new QueryRequestOptions() { PartitionKey = new PartitionKey("base") }))
  130. {
  131. using var json = await JsonDocument.ParseAsync(item.ContentStream);
  132. if (json.RootElement.TryGetProperty("_count", out JsonElement count) && count.GetUInt16() > 0)
  133. {
  134. foreach (var doc in json.RootElement.GetProperty("Documents").EnumerateArray())
  135. {
  136. string id = doc.GetProperty("id").GetString();
  137. if(!tmids.Contains(id)) tmids.Add(id);
  138. //基本資料
  139. TmidStics tmidStics = (tmidDic.ContainsKey(id)) ? tmidDic[id] : new() { id = id };
  140. if (eventList.Contains("ies5")) tmidStics.ies5 = new();
  141. if (eventList.Contains("points")) tmidStics.points = new();
  142. if (eventList.Contains("sokrates")) tmidStics.sokrates = new();
  143. if (eventList.Contains("benefits")) tmidStics.benefits = new();
  144. if (eventList.Contains("iot")) tmidStics.iot = new();
  145. tmidStics.name = doc.GetProperty("name").GetString();
  146. tmidStics.picture = doc.GetProperty("picture").GetString();
  147. tmidStics.mobile = GenDataMask(doc.GetProperty("mobile").GetString(), "mobile");
  148. tmidStics.mail = GenDataMask(doc.GetProperty("mail").GetString(), "mail");
  149. tmidStics.lang = (doc.TryGetProperty("lang", out JsonElement lang)) ? lang.GetString() : string.Empty;
  150. tmidStics.wechat = (doc.TryGetProperty("wechat", out JsonElement wechat) && !string.IsNullOrWhiteSpace(wechat.GetString())) ? true : false;
  151. tmidStics.facebook = (doc.TryGetProperty("facebook", out JsonElement facebook) && !string.IsNullOrWhiteSpace(facebook.GetString())) ? true : false;
  152. tmidStics.google = (doc.TryGetProperty("google", out JsonElement google) && !string.IsNullOrWhiteSpace(google.GetString())) ? true : false;
  153. tmidStics.ding = (doc.TryGetProperty("ding", out JsonElement ding) && !string.IsNullOrWhiteSpace(ding.GetString())) ? true : false;
  154. tmidStics.apple = (doc.TryGetProperty("apple", out JsonElement apple) && !string.IsNullOrWhiteSpace(apple.GetString())) ? true : false;
  155. tmidStics.ts = (doc.TryGetProperty("ts", out JsonElement ts)) ? ts.GetInt64() : 0;
  156. tmidDic.Add(id, tmidStics);
  157. }
  158. }
  159. }
  160. //取得TMID進階資料
  161. regiondata regionData = GetRegionData(); //取得國省市區地理資訊架構
  162. query = new QueryDefinition(@"SELECT c.id, c.name, c.mobile, c.mail, c.country, c.province, c.city, c.schoolCode, c.schoolCodeW, c.unitType, c.unitName, c.jobTitle FROM c WHERE (ARRAY_CONTAINS(@key, c.id) OR ARRAY_CONTAINS(@key, c.mobile))")
  163. .WithParameter("@key", tmids);
  164. await foreach (var item in cosmosClientCsv2
  165. .GetContainer("Core", "ID2")
  166. .GetItemQueryStreamIterator(query, requestOptions: new QueryRequestOptions() { PartitionKey = new PartitionKey("base-ex") }))
  167. {
  168. using var json = await JsonDocument.ParseAsync(item.ContentStream);
  169. if (json.RootElement.TryGetProperty("_count", out JsonElement count) && count.GetUInt16() > 0)
  170. {
  171. foreach (var doc in json.RootElement.GetProperty("Documents").EnumerateArray())
  172. {
  173. string id = doc.GetProperty("id").GetString();
  174. TmidStics tmidStics = (tmidDic.ContainsKey(id)) ? tmidDic[id] : new() { id = id };
  175. if (eventList.Contains("ies5")) tmidStics.ies5 = new();
  176. if (eventList.Contains("points")) tmidStics.points = new();
  177. if (eventList.Contains("sokrates")) tmidStics.sokrates = new();
  178. if (eventList.Contains("benefits")) tmidStics.benefits = new();
  179. if (eventList.Contains("iot")) tmidStics.iot = new();
  180. if (string.IsNullOrWhiteSpace(tmidStics.name)) tmidStics.name = doc.GetProperty("name").GetString();
  181. if (string.IsNullOrWhiteSpace(tmidStics.mobile)) tmidStics.mobile = GenDataMask(doc.GetProperty("mobile").GetString(), "mobile");
  182. if (string.IsNullOrWhiteSpace(tmidStics.mail)) tmidStics.mail = GenDataMask(doc.GetProperty("mail").GetString(), "mail");
  183. string country = doc.GetProperty("country").GetString();
  184. string province = doc.GetProperty("province").GetString();
  185. string city = doc.GetProperty("city").GetString();
  186. string district = string.Empty;
  187. if (!string.IsNullOrWhiteSpace(country) && country.Equals("TW"))
  188. {
  189. district = city;
  190. city = province;
  191. province = string.Empty;
  192. }
  193. tmidStics.country = (!string.IsNullOrWhiteSpace(country) && regionData.country.ContainsKey(country)) ? regionData.country[country].name.Replace("地區", "").Replace("地区", "") : country;
  194. tmidStics.province = (!string.IsNullOrWhiteSpace(country) && regionData.country.ContainsKey(country) && !string.IsNullOrWhiteSpace(province) && regionData.province.ContainsKey(country) && regionData.province[country].ContainsKey(province)) ? regionData.province[country][province].name : province;
  195. if (!string.IsNullOrWhiteSpace(country) && country.Equals("TW"))
  196. tmidStics.city = (regionData.city.ContainsKey(country) && !string.IsNullOrWhiteSpace(city) && regionData.city[country]["tw"].ContainsKey(city)) ? regionData.city[country]["tw"][city].name : city;
  197. else if (!string.IsNullOrWhiteSpace(country) && country.Equals("CN"))
  198. tmidStics.city = (regionData.city.ContainsKey(country) && !string.IsNullOrWhiteSpace(province) && !string.IsNullOrWhiteSpace(city) && regionData.city[country].ContainsKey(province) && regionData.city[country][province].ContainsKey(city)) ? regionData.city[country][province][city].name : city;
  199. tmidStics.dist = (!string.IsNullOrWhiteSpace(district) && country.Equals("TW") && regionData.city[country]["tw"].ContainsKey(city) && regionData.dist[country]["tw"][city].ContainsKey(district)) ? regionData.dist[country]["tw"][city][district].name : district;
  200. tmidStics.schoolCode = (doc.TryGetProperty("schoolCode", out JsonElement schCode)) ? schCode.GetString() : string.Empty;
  201. tmidStics.schoolCodeW = (doc.TryGetProperty("schoolCodeW", out JsonElement schCodeW)) ? schCodeW.GetString() : string.Empty;
  202. tmidStics.unitType = (doc.TryGetProperty("unitType", out JsonElement unitType)) ? unitType.GetString() : string.Empty;
  203. tmidStics.unitName = (doc.TryGetProperty("unitName", out JsonElement unitName)) ? unitName.GetString() : string.Empty;
  204. tmidStics.jobTitle = (doc.TryGetProperty("jobTitle", out JsonElement jobTitle)) ? jobTitle.GetString() : string.Empty;
  205. }
  206. }
  207. }
  208. if(tmidDic.Count.Equals(0)) return Ok(new List<object>());
  209. //取得 票券、登入各服務的時間、取得積分、個人服務授權、IOT
  210. foreach (KeyValuePair<string, TmidStics> dicItem in tmidDic)
  211. {
  212. string id = dicItem.Key;
  213. TmidStics tmidStics = dicItem.Value;
  214. //票券
  215. if (eventList.Contains("coupons"))
  216. {
  217. var usersCoupons = tableCouponClient.Get<DynamicTableEntity>(id);
  218. foreach (var coupon in usersCoupons)
  219. {
  220. tmidStics.coupons.Add(
  221. new TmidCoupon()
  222. {
  223. code = coupon.RowKey,
  224. exchange = coupon.Properties["exchangeTime"].DateTimeOffsetValue.Value.ToUnixTimeSeconds(),
  225. });
  226. }
  227. }
  228. //取得登入各服務的時間
  229. if (eventList.Contains("login"))
  230. {
  231. var loginTime = await redisClient.HashGetAllAsync(id);
  232. foreach (var t in loginTime)
  233. {
  234. if (!t.Name.StartsWith("HiTeach") && !_dicClientIds.ContainsKey(t.Name)) continue;
  235. tmidStics.login.Add(
  236. new TmidLoginTime()
  237. {
  238. product = t.Name.StartsWith("HiTeach") ? t.Name.ToString().Split("-")[0] : _dicClientIds[t.Name],
  239. time = (long)t.Value
  240. });
  241. }
  242. }
  243. //個人服務授權
  244. if (eventList.Contains("prod"))
  245. {
  246. tmidStics.prod = await getTMIDAuthService(cosmosClientCsv2, id, "", true);
  247. }
  248. //IOT
  249. if (eventList.Contains("iot"))
  250. {
  251. tmidStics.iot.hiteach.year = null;
  252. tmidStics.iot.hiteach.month = null;
  253. tmidStics.iot.hiteach.day = null;
  254. if (!string.IsNullOrWhiteSpace(dateFromStr) && !string.IsNullOrWhiteSpace(dateToStr) && !string.IsNullOrWhiteSpace(dateUnit))
  255. {
  256. if (dateUnit.ToLower().Equals("year"))
  257. {
  258. tmidStics.iot.hiteach.year = await getTMIDIotData(cosmosClientIes5, id, "HiTeach", "year", y, 0, 0, dateTimeFromSec, dateTimeToSec);
  259. tmidStics.iot.hiteachcc.year = await getTMIDIotData(cosmosClientIes5, id, "HiTeachCC", "year", y, 0, 0, dateTimeFromSec, dateTimeToSec);
  260. }
  261. else if (dateUnit.ToLower().Equals("month"))
  262. {
  263. tmidStics.iot.hiteach.month = await getTMIDIotData(cosmosClientIes5, id, "HiTeach", "month", y, m, 0, dateTimeFromSec, dateTimeToSec);
  264. tmidStics.iot.hiteachcc.month = await getTMIDIotData(cosmosClientIes5, id, "HiTeachCC", "month", y, m, 0, dateTimeFromSec, dateTimeToSec);
  265. }
  266. else if (dateUnit.ToLower().Equals("day"))
  267. {
  268. tmidStics.iot.hiteach.day = await getTMIDIotData(cosmosClientIes5, id, "HiTeach", "day", y, m, d, dateTimeFromSec, dateTimeToSec);
  269. tmidStics.iot.hiteachcc.day = await getTMIDIotData(cosmosClientIes5, id, "HiTeachCC", "day", y, m, d, dateTimeFromSec, dateTimeToSec);
  270. }
  271. }
  272. else
  273. {
  274. tmidStics.iot.hiteach.year = await getTMIDIotData(cosmosClientIes5, id, "HiTeach", "year", y, 0, 0, dateTimeFromSec, dateTimeToSec);
  275. tmidStics.iot.hiteach.month = await getTMIDIotData(cosmosClientIes5, id, "HiTeach", "month", y, m, 0, dateTimeFromSec, dateTimeToSec);
  276. tmidStics.iot.hiteach.day = await getTMIDIotData(cosmosClientIes5, id, "HiTeach", "day", y, m, d, dateTimeFromSec, dateTimeToSec);
  277. tmidStics.iot.hiteachcc.year = await getTMIDIotData(cosmosClientIes5, id, "HiTeachCC", "year", y, 0, 0, dateTimeFromSec, dateTimeToSec);
  278. tmidStics.iot.hiteachcc.month = await getTMIDIotData(cosmosClientIes5, id, "HiTeachCC", "month", y, m, 0, dateTimeFromSec, dateTimeToSec);
  279. tmidStics.iot.hiteachcc.day = await getTMIDIotData(cosmosClientIes5, id, "HiTeachCC", "day", y, m, d, dateTimeFromSec, dateTimeToSec);
  280. }
  281. }
  282. }
  283. //積分
  284. if (eventList.Contains("points"))
  285. {
  286. List<string> ids = tmidDic.Keys.ToList();
  287. //防止Query過長,分次取得
  288. Dictionary<int, List<string>> idsDic = new Dictionary<int, List<string>>();
  289. int idCount = 0;
  290. int max = 100; //要分割成多少人一組
  291. foreach (string id in ids)
  292. {
  293. int indx = idCount / max;
  294. if (!idsDic.ContainsKey(indx)) idsDic.Add(indx, new List<string>());
  295. idsDic[indx].Add(id);
  296. idCount++;
  297. }
  298. foreach(var itemd in idsDic)
  299. {
  300. string filter = TableQuery.GenerateFilterCondition("PartitionKey", QueryComparisons.Equal, "Points");
  301. string rkFilterCombine = string.Empty;
  302. foreach (string rowKey in itemd.Value)
  303. {
  304. string rkFilter = TableQuery.GenerateFilterCondition("RowKey", QueryComparisons.Equal, rowKey);
  305. if (string.IsNullOrWhiteSpace(rkFilterCombine))
  306. {
  307. rkFilterCombine = rkFilter;
  308. }
  309. else
  310. {
  311. rkFilterCombine = TableQuery.CombineFilters(rkFilterCombine, TableOperators.Or, rkFilter);
  312. }
  313. }
  314. filter = TableQuery.CombineFilters(filter, TableOperators.And, rkFilterCombine);
  315. TableQuery tableQuery = new TableQuery().Where(filter);
  316. var usersPoints = tablePointsClient.ExecuteQuery(tableQuery);
  317. if (usersPoints.Any())
  318. {
  319. foreach (DynamicTableEntity item in usersPoints)
  320. {
  321. string id = item.RowKey.ToString();
  322. TmidStics tmidStics = tmidDic[id];
  323. tmidStics.points.points = (usersPoints != null) ? item.Properties["Points"].Int32Value.Value : 0;
  324. tmidStics.points.level = (usersPoints != null) ? item.Properties["Level"].Int32Value.Value : 0;
  325. tmidStics.points.balance = (usersPoints != null) ? item.Properties["Balance"].Int32Value.Value : 0;
  326. }
  327. }
  328. }
  329. }
  330. //IES5
  331. if (eventList.Contains("ies5"))
  332. {
  333. query = new QueryDefinition(@"SELECT c.id, c.defaultSchool, c.schools FROM c WHERE ARRAY_CONTAINS(@key, c.id)")
  334. .WithParameter("@key", tmidDic.Keys.ToList());
  335. await foreach (var item in cosmosClientIes5
  336. .GetContainer(Constant.TEAMModelOS, "Teacher")
  337. .GetItemQueryStreamIterator(query, requestOptions: new QueryRequestOptions() { PartitionKey = new PartitionKey("Base") }))
  338. {
  339. using var json = await JsonDocument.ParseAsync(item.ContentStream);
  340. if (json.RootElement.TryGetProperty("_count", out JsonElement count) && count.GetUInt16() > 0)
  341. {
  342. foreach (var doc in json.RootElement.GetProperty("Documents").EnumerateArray())
  343. {
  344. string id = doc.GetProperty("id").GetString();
  345. TmidStics tmidStics = tmidDic[id];
  346. //IES5學校資訊
  347. string defaultschool = (doc.TryGetProperty("defaultSchool", out JsonElement _defaultSchool)) ? _defaultSchool.ToString() : string.Empty; //預設學校
  348. List<string> schoolIds = new List<string>();
  349. if (doc.TryGetProperty("schools", out JsonElement schoolsJobj))
  350. {
  351. foreach (var obj in schoolsJobj.EnumerateArray())
  352. {
  353. string schoolCodeNow = $"{obj.GetProperty("schoolId")}";
  354. string status = $"{obj.GetProperty("status")}";
  355. if (status.Equals("join"))
  356. {
  357. schoolIds.Add(schoolCodeNow); //放入學校ID列,一次取
  358. }
  359. }
  360. }
  361. if (schoolIds.Count > 0)
  362. {
  363. QueryDefinition querysc = new QueryDefinition(@"SELECT c.id, c.name, c.region, c.province, c.city, c.dist, c.size, c.period FROM c WHERE ARRAY_CONTAINS(@key, c.id)")
  364. .WithParameter("@key", schoolIds);
  365. await foreach (var itemsc in cosmosClientIes5
  366. .GetContainer(Constant.TEAMModelOS, "School")
  367. .GetItemQueryStreamIterator(querysc, requestOptions: new QueryRequestOptions() { PartitionKey = new PartitionKey("Base") }))
  368. {
  369. using var jsonsc = await JsonDocument.ParseAsync(itemsc.ContentStream);
  370. if (jsonsc.RootElement.TryGetProperty("_count", out JsonElement countsc) && countsc.GetUInt16() > 0)
  371. {
  372. foreach (var school in jsonsc.RootElement.GetProperty("Documents").EnumerateArray())
  373. {
  374. string schoolCodeNow = school.GetProperty("id").GetString();
  375. //當前學年 ※若有多學段,取第一個學段來取得學年資訊吧
  376. Period period = school.GetProperty("period").Deserialize<List<Period>>().First();
  377. var info = SchoolService.GetSemester(period, 0, DateTime.Now.ToString());
  378. int studyYear = info.studyYear;
  379. //學校Blob使用狀況
  380. (long usedSize, long teach, long total, long surplus, Dictionary<string, double?> catalog) schoolUsedBlob = await BlobService.GetSurplusSpace(schoolCodeNow, "school", _option.Location, _azureCosmos, _azureRedis, _azureStorage, _dingDing, _httpTrigger);
  381. //老師在此學校的課程數 ※只取當前學年
  382. int courseCnt = 0;
  383. var queryCrs = $"SELECT VALUE COUNT(1) FROM ( SELECT DISTINCT VALUE(c.id) FROM c JOIN schedules IN c.schedules WHERE schedules.teacherId = '{id}' AND c.year = {studyYear} )";
  384. await foreach (int itemCrs in cosmosClientIes5.GetContainer(Constant.TEAMModelOS, "School").GetItemQueryIterator<int>(queryText: queryCrs, requestOptions: new QueryRequestOptions() { PartitionKey = new PartitionKey($"CourseTask-{schoolCodeNow}") }))
  385. {
  386. courseCnt = itemCrs;
  387. }
  388. //IES5學校資料製作
  389. TmidSticsIes5School schoolobj = new TmidSticsIes5School();
  390. long ssize = (school.TryGetProperty("size", out JsonElement ssizeJson)) ? ssizeJson.GetInt32() : 0;
  391. long sused = schoolUsedBlob.usedSize + schoolUsedBlob.teach * 1073741824;
  392. long stotal = ssize * 1073741824;
  393. object schzize = new
  394. {
  395. used = sused,
  396. total = stotal,
  397. avaliable = stotal - sused,
  398. };
  399. schoolobj.schoolId = schoolCodeNow;
  400. schoolobj.name = school.GetProperty("name").GetString();
  401. schoolobj.region = school.GetProperty("region").GetString();
  402. schoolobj.province = school.GetProperty("province").GetString();
  403. schoolobj.city = school.GetProperty("city").GetString();
  404. schoolobj.dist = school.GetProperty("dist").GetString();
  405. schoolobj.size = schzize;
  406. schoolobj.courseCnt = courseCnt;
  407. schoolobj.defaultSchool = (!string.IsNullOrWhiteSpace(defaultschool) && defaultschool.Equals(schoolCodeNow)) ? true : false;
  408. tmidStics.ies5.schools.Add(schoolobj);
  409. }
  410. }
  411. }
  412. }
  413. //IES5個人空間
  414. var (usedSize, teach, total, surplus, catalog) = await BlobService.GetSurplusSpace(id, "private", _option.Location, _azureCosmos, _azureRedis, _azureStorage, _dingDing, _httpTrigger);
  415. tmidStics.ies5.usedSize = usedSize;
  416. tmidStics.ies5.teachSize = teach * 1073741824;
  417. tmidStics.ies5.totalSize = total * 1073741824;
  418. tmidStics.ies5.surplusSize = surplus;
  419. //IES5個人資源數
  420. tmidStics.ies5.resCount = 0; //教材數
  421. tmidStics.ies5.itemCount = 0; //題目數
  422. tmidStics.ies5.paperCount = 0; //試卷數
  423. ///IES5個人題目數
  424. await foreach (int itemC in cosmosClientIes5.GetContainer(Constant.TEAMModelOS, "Teacher").GetItemQueryIterator<int>(queryText: $"SELECT VALUE COUNT(1) FROM c WHERE c.pid = null", requestOptions: new QueryRequestOptions() { PartitionKey = new PartitionKey($"Item-{id}") }))
  425. {
  426. tmidStics.ies5.itemCount = itemC;
  427. }
  428. ///IES5個人試卷數
  429. await foreach (int paperC in cosmosClientIes5.GetContainer(Constant.TEAMModelOS, "Teacher").GetItemQueryIterator<int>(queryText: $"SELECT VALUE COUNT(1) FROM c WHERE c.scope = 'private'", requestOptions: new QueryRequestOptions() { PartitionKey = new PartitionKey($"Paper-{id}") }))
  430. {
  431. tmidStics.ies5.paperCount = paperC;
  432. }
  433. ///IES5個人教材數
  434. List<BlobService.BlobCount> blogCountList = await BlobService.BloblogCount(cosmosClientIes5, "private", id, "", "");
  435. foreach (BlobService.BlobCount blobCountRow in blogCountList)
  436. {
  437. switch (blobCountRow.type)
  438. {
  439. case "doc":
  440. case "image":
  441. case "res":
  442. case "video":
  443. case "audio":
  444. case "other":
  445. tmidStics.ies5.resCount += blobCountRow.count;
  446. break;
  447. }
  448. }
  449. if (!tmidDic.ContainsKey(id)) tmidDic.Add(id, tmidStics);
  450. }
  451. }
  452. }
  453. }
  454. //個人權益
  455. if (eventList.Contains("benefits"))
  456. {
  457. query = new QueryDefinition(@"SELECT * FROM c WHERE (ARRAY_CONTAINS(@key, c.id))")
  458. .WithParameter("@key", tmidDic.Keys.ToList());
  459. await foreach (var item in cosmosClientCsv2
  460. .GetContainer("Core", "ID2")
  461. .GetItemQueryStreamIterator(query, requestOptions: new QueryRequestOptions() { PartitionKey = new PartitionKey("benefits") }))
  462. {
  463. using var json = await JsonDocument.ParseAsync(item.ContentStream);
  464. if (json.RootElement.TryGetProperty("_count", out JsonElement count) && count.GetUInt16() > 0)
  465. {
  466. foreach (var doc in json.RootElement.GetProperty("Documents").EnumerateArray())
  467. {
  468. string id = doc.GetProperty("id").GetString();
  469. if (doc.TryGetProperty("hiteach", out var elementHiteachData))
  470. {
  471. tmidDic[id].benefits.hiteach = elementHiteachData.ToObject<List<object>>();
  472. }
  473. if (doc.TryGetProperty("hiteachcc", out var elementHiteachCCData))
  474. {
  475. tmidDic[id].benefits.hiteachcc = elementHiteachCCData.ToObject<List<object>>();
  476. }
  477. }
  478. }
  479. }
  480. }
  481. //蘇格拉底資料
  482. if (eventList.Contains("sokrates"))
  483. {
  484. query = new QueryDefinition(@"SELECT * FROM c WHERE (ARRAY_CONTAINS(@key, c.id))")
  485. .WithParameter("@key", tmidDic.Keys.ToList());
  486. await foreach (var item in cosmosClientCsv2
  487. .GetContainer("Core", "ID2")
  488. .GetItemQueryStreamIterator(query, requestOptions: new QueryRequestOptions() { PartitionKey = new PartitionKey("sokrates") }))
  489. {
  490. using var json = await JsonDocument.ParseAsync(item.ContentStream);
  491. if (json.RootElement.TryGetProperty("_count", out JsonElement count) && count.GetUInt16() > 0)
  492. {
  493. foreach (var doc in json.RootElement.GetProperty("Documents").EnumerateArray())
  494. {
  495. string id = doc.GetProperty("id").GetString();
  496. if (doc.TryGetProperty("hiteach_data", out var elementHiteachData))
  497. {
  498. tmidDic[id].sokrates.hiteach_data = elementHiteachData.ToObject<TmidSokratesHiteach>();
  499. }
  500. if (doc.TryGetProperty("user_channels", out var elementUserChannels))
  501. {
  502. tmidDic[id].sokrates.user_channels = elementUserChannels.ToObject<object>();
  503. }
  504. }
  505. }
  506. }
  507. }
  508. //輸出
  509. List<object> data = new();
  510. foreach (KeyValuePair<string, TmidStics> dicItem in tmidDic)
  511. {
  512. data.Add(dicItem.Value);
  513. }
  514. return Ok(data);
  515. }
  516. catch (Exception ex)
  517. {
  518. //await _dingDing.SendBotMsg($"BI,{_option.Location} /tmid/get-tmidstics \n {ex.Message}\n{ex.StackTrace}", GroupNames.台北開發測試群組);
  519. return BadRequest();
  520. }
  521. }
  522. /// <summary>
  523. /// 取得TMID使用產品資料
  524. /// </summary>
  525. /// <param name="jsonElement"></param>
  526. /// <returns></returns>
  527. [ProducesDefaultResponseType]
  528. //[AuthToken(Roles = "admin")]
  529. [HttpPost("get-tmid-useprod")]
  530. public async Task<IActionResult> GetTmidUseProd(JsonElement jsonElement)
  531. {
  532. Dictionary<string, TmidStics> tmidDic = new();
  533. HashSet<string> schIds = new HashSet<string>();
  534. string dateFromStr = (jsonElement.TryGetProperty("dateFrom", out JsonElement dateFromJobj)) ? dateFromJobj.GetString() : string.Empty; //查詢日期:起始(string)[例]2023-03-05
  535. string dateToStr = (jsonElement.TryGetProperty("dateTo", out JsonElement dateToJobj)) ? dateToJobj.GetString() : string.Empty; //查詢日期:結束(string)[例]2023-03-27
  536. if(string.IsNullOrWhiteSpace(dateFromStr) || string.IsNullOrWhiteSpace(dateToStr)) return BadRequest();
  537. DateTime dateFrom = DateTime.ParseExact(dateFromStr, "yyyy-MM-dd", null);
  538. DateTime dateTo = DateTime.ParseExact(dateToStr, "yyyy-MM-dd", null).Add(new TimeSpan(0, 23, 59, 59));
  539. DateTimeOffset dateTimeFrom = new DateTimeOffset(dateFrom, TimeSpan.Zero);
  540. DateTimeOffset dateTimeTo = new DateTimeOffset(dateTo, TimeSpan.Zero);
  541. long unixTimeFrom = dateTimeFrom.ToUnixTimeSeconds();
  542. long unixTimeTo = dateTimeTo.ToUnixTimeSeconds();
  543. //大陸站 ID計算方式特化處理
  544. if (_option.Location.Contains("China"))
  545. {
  546. unixTimeFrom += 5000000000;
  547. unixTimeTo += 5000000000;
  548. }
  549. //服務Client端
  550. var cosmosClientIes5 = _azureCosmos.GetCosmosClient(); //CosmosDB IES5
  551. var cosmosClientCsv2 = _azureCosmos.GetCosmosClient(name: "CoreServiceV2"); //CosmosDB CSV2
  552. var cosmosClientCsv2CnRead = _azureCosmos.GetCosmosClient(name: "CoreServiceV2CnRead"); //CosmosDB CSV2 CN Read
  553. var storageClientCsv2 = _azureStorage.GetCloudTableClient(name: "CoreServiceV2"); //Storage CSV2
  554. var tableCouponClient = storageClientCsv2.GetTableReference("Coupon");
  555. var tablePointsClient = storageClientCsv2.GetTableReference("Points");
  556. var redisClient = _azureRedis.GetRedisClient(4);
  557. //取得TMID資料
  558. HashSet<string> tmids = new HashSet<string>();
  559. int pageSize = 100;
  560. string? continuationToken = null;
  561. string query = $"SELECT * FROM c WHERE {unixTimeFrom} <= StringToNumber(c.id) AND StringToNumber(c.id) <= {unixTimeTo}";
  562. do
  563. {
  564. await foreach (var item in cosmosClientCsv2
  565. .GetContainer("Core", "ID2")
  566. .GetItemQueryStreamIterator(query, continuationToken: continuationToken, requestOptions: new QueryRequestOptions() { PartitionKey = new PartitionKey("base"), MaxItemCount = pageSize }))
  567. {
  568. using var json = await JsonDocument.ParseAsync(item.ContentStream);
  569. if (json.RootElement.TryGetProperty("_count", out JsonElement count) && count.GetUInt16() > 0)
  570. {
  571. foreach (var doc in json.RootElement.GetProperty("Documents").EnumerateArray())
  572. {
  573. string id = doc.GetProperty("id").GetString();
  574. if(!tmidDic.ContainsKey(id)) tmidDic.Add(id, new TmidStics());
  575. tmids.Add(id);
  576. TmidStics tmidStics = tmidDic[id];
  577. //基本資料
  578. tmidStics.name = doc.GetProperty("name").GetString();
  579. //取得登入各服務的時間
  580. var loginTime = await redisClient.HashGetAllAsync(id);
  581. foreach (var t in loginTime)
  582. {
  583. if (!t.Name.StartsWith("HiTeach") && !_dicClientIds.ContainsKey(t.Name)) continue;
  584. tmidStics.login.Add(
  585. new TmidLoginTime()
  586. {
  587. product = t.Name.StartsWith("HiTeach") ? t.Name.ToString().Split("-")[0] : _dicClientIds[t.Name],
  588. time = (long)t.Value
  589. });
  590. }
  591. }
  592. }
  593. continuationToken = item.GetContinuationToken();
  594. }
  595. }
  596. while (!string.IsNullOrWhiteSpace(continuationToken));
  597. //取得TMID進階資料
  598. continuationToken = null;
  599. do
  600. {
  601. QueryDefinition querye = new QueryDefinition(@"SELECT c.id, c.name, c.mobile, c.mail, c.country, c.province, c.city, c.schoolCode, c.schoolCodeW, c.unitType, c.unitName, c.jobTitle FROM c WHERE (ARRAY_CONTAINS(@key, c.id) OR ARRAY_CONTAINS(@key, c.mobile))")
  602. .WithParameter("@key", tmids);
  603. await foreach (var item in cosmosClientCsv2
  604. .GetContainer("Core", "ID2")
  605. .GetItemQueryStreamIterator(querye, continuationToken: continuationToken, requestOptions: new QueryRequestOptions() { PartitionKey = new PartitionKey("base-ex"), MaxItemCount = pageSize }))
  606. {
  607. using var json = await JsonDocument.ParseAsync(item.ContentStream);
  608. if (json.RootElement.TryGetProperty("_count", out JsonElement count) && count.GetUInt16() > 0)
  609. {
  610. foreach (var doc in json.RootElement.GetProperty("Documents").EnumerateArray())
  611. {
  612. string id = doc.GetProperty("id").GetString();
  613. TmidStics tmidStics = tmidDic[id];
  614. tmidStics.schoolCode = (doc.TryGetProperty("schoolCode", out JsonElement schCode)) ? schCode.GetString() : string.Empty;
  615. tmidStics.schoolCodeW = (doc.TryGetProperty("schoolCodeW", out JsonElement schCodeW)) ? schCodeW.GetString() : string.Empty;
  616. if (!string.IsNullOrWhiteSpace(tmidStics.schoolCode)) schIds.Add(tmidStics.schoolCode);
  617. if (!string.IsNullOrWhiteSpace(tmidStics.schoolCodeW)) schIds.Add(tmidStics.schoolCodeW);
  618. if(!string.IsNullOrWhiteSpace(tmidStics.schoolCode) && string.IsNullOrWhiteSpace(tmidStics.schoolId))
  619. {
  620. tmidStics.schoolId = tmidStics.schoolCode;
  621. } else if(!string.IsNullOrWhiteSpace(tmidStics.schoolCodeW) && string.IsNullOrWhiteSpace(tmidStics.schoolId))
  622. {
  623. tmidStics.schoolId = tmidStics.schoolCodeW;
  624. }
  625. }
  626. }
  627. continuationToken = item.GetContinuationToken();
  628. }
  629. }
  630. while (!string.IsNullOrWhiteSpace(continuationToken));
  631. //取得學校資料
  632. Dictionary<string, Dictionary<string, string>> school = new Dictionary<string, Dictionary<string, string>>();
  633. string querys = $"SELECT * FROM c WHERE ARRAY_CONTAINS({JsonSerializer.Serialize(schIds)}, c.shortCode, true)";
  634. await foreach (var item in cosmosClientCsv2CnRead
  635. .GetContainer("Core", "School")
  636. .GetItemQueryStreamIterator(querys, requestOptions: new QueryRequestOptions() { PartitionKey = new PartitionKey("base") }))
  637. {
  638. using var json = await JsonDocument.ParseAsync(item.ContentStream);
  639. if (json.RootElement.TryGetProperty("_count", out JsonElement count) && count.GetUInt16() > 0)
  640. {
  641. foreach (var doc in json.RootElement.GetProperty("Documents").EnumerateArray())
  642. {
  643. Dictionary<string, string> schoolRow = new Dictionary<string, string>();
  644. string shortCode = doc.GetProperty("shortCode").GetString();
  645. string name = doc.GetProperty("name").GetString();
  646. string countryId = doc.GetProperty("countryId").GetString();
  647. string countryName = doc.GetProperty("countryName").GetString();
  648. string provinceId = doc.GetProperty("provinceId").GetString();
  649. string provinceName = doc.GetProperty("provinceName").GetString();
  650. string cityId = doc.GetProperty("cityId").GetString();
  651. string cityName = doc.GetProperty("cityName").GetString();
  652. string distId = doc.GetProperty("distId").GetString();
  653. string distName = doc.GetProperty("distName").GetString();
  654. schoolRow.Add("shortCode", shortCode);
  655. schoolRow.Add("name", name);
  656. schoolRow.Add("countryId", countryId);
  657. schoolRow.Add("countryName", countryName);
  658. schoolRow.Add("provinceId", provinceId);
  659. schoolRow.Add("provinceName", provinceName);
  660. schoolRow.Add("cityId", cityId);
  661. schoolRow.Add("cityName", cityName);
  662. schoolRow.Add("distId", distId);
  663. schoolRow.Add("distName", distName);
  664. school.Add(shortCode, schoolRow);
  665. }
  666. }
  667. }
  668. //資料整理
  669. foreach(var item in tmidDic)
  670. {
  671. TmidStics tmidStics = item.Value;
  672. if (!string.IsNullOrWhiteSpace(tmidStics.schoolCode) || !string.IsNullOrWhiteSpace(tmidStics.schoolCodeW))
  673. {
  674. if (!string.IsNullOrWhiteSpace(tmidStics.schoolCode))
  675. {
  676. tmidStics.schoolId = tmidStics.schoolCode;
  677. }
  678. else if (!string.IsNullOrWhiteSpace(tmidStics.schoolCodeW))
  679. {
  680. tmidStics.schoolId = tmidStics.schoolCodeW;
  681. }
  682. }
  683. }
  684. //返回值整理
  685. List<TmidIotProdCnt> tmid = new List<TmidIotProdCnt>();
  686. List<TmidIotProdCntGeo> sch = new List<TmidIotProdCntGeo>();
  687. foreach (var item in tmidDic)
  688. {
  689. //TMID
  690. string id = item.Key;
  691. TmidIotProdCnt tmidRow = new TmidIotProdCnt();
  692. tmidRow.id = id;
  693. tmidRow.name = item.Value.name;
  694. tmidRow.schid = (!string.IsNullOrWhiteSpace(item.Value.schoolId)) ? item.Value.schoolId : null;
  695. foreach (TmidLoginTime login in item.Value.login)
  696. {
  697. switch (login.product)
  698. {
  699. case "HiTeach":
  700. tmidRow.hiteach++;
  701. break;
  702. case "HiTeachCC":
  703. tmidRow.hiteachcc++;
  704. break;
  705. case "HiTA":
  706. tmidRow.hita++;
  707. break;
  708. case "IES5":
  709. tmidRow.ies5++;
  710. break;
  711. case "Account":
  712. tmidRow.account++;
  713. break;
  714. case "Sokrates":
  715. tmidRow.sokrates++;
  716. break;
  717. case "SokAPP":
  718. tmidRow.sokapp++;
  719. break;
  720. case "IRS":
  721. tmidRow.irs++;
  722. break;
  723. }
  724. }
  725. tmid.Add(tmidRow);
  726. //school
  727. if(!string.IsNullOrWhiteSpace(tmidRow.schid))
  728. {
  729. var schRow = sch.Where(s => s.id.Equals(tmidRow.schid)).FirstOrDefault();
  730. if(schRow == null)
  731. {
  732. TmidIotProdCntGeo tmpData = new TmidIotProdCntGeo();
  733. tmpData.id = tmidRow.schid;
  734. tmpData.schid = tmidRow.schid;
  735. if(school.ContainsKey(tmidRow.schid))
  736. {
  737. tmpData.name = school[tmidRow.schid]["name"];
  738. tmpData.countryId = school[tmidRow.schid]["countryId"];
  739. tmpData.countryName = school[tmidRow.schid]["countryName"];
  740. tmpData.provinceId = school[tmidRow.schid]["provinceId"];
  741. tmpData.provinceName = school[tmidRow.schid]["provinceName"];
  742. tmpData.cityId = school[tmidRow.schid]["cityId"];
  743. tmpData.cityName = school[tmidRow.schid]["cityName"];
  744. tmpData.distId = school[tmidRow.schid]["distId"];
  745. tmpData.distName = school[tmidRow.schid]["distName"];
  746. }
  747. sch.Add(tmpData);
  748. schRow = sch.Where(s => s.id.Equals(tmidRow.schid)).FirstOrDefault();
  749. }
  750. if (tmidRow.hiteach > 0) schRow.hiteach++;
  751. if (tmidRow.hiteachcc > 0) schRow.hiteachcc++;
  752. if (tmidRow.hita > 0) schRow.hita++;
  753. if (tmidRow.ies5 > 0) schRow.ies5++;
  754. if (tmidRow.account > 0) schRow.account++;
  755. if (tmidRow.sokrates > 0) schRow.sokrates++;
  756. if (tmidRow.sokapp > 0) schRow.sokapp++;
  757. if (tmidRow.irs > 0) schRow.irs++;
  758. if (!schRow.tmids.Contains(id)) schRow.tmids.Add(id);
  759. }
  760. }
  761. //geo
  762. List<TmidIotProdCntGeo> geo = new List<TmidIotProdCntGeo>();
  763. foreach(TmidIotProdCntGeo schRow in sch)
  764. {
  765. string countryId = (!string.IsNullOrWhiteSpace(schRow.countryId)) ? schRow.countryId : string.Empty;
  766. string provinceId = (!string.IsNullOrWhiteSpace(schRow.provinceId)) ? schRow.provinceId : string.Empty;
  767. string cityId = (!string.IsNullOrWhiteSpace(schRow.cityId)) ? schRow.cityId : string.Empty;
  768. string distId = (!string.IsNullOrWhiteSpace(schRow.distId)) ? schRow.distId : string.Empty;
  769. string geoid = $"{countryId}-{provinceId}-{cityId}-{distId}";
  770. var geoRow = geo.Where(s => s.id.Equals(geoid)).FirstOrDefault();
  771. if(geoRow == null)
  772. {
  773. geo.Add(new TmidIotProdCntGeo()
  774. {
  775. id = geoid,
  776. name = string.Empty,
  777. countryId = schRow.countryId,
  778. countryName = schRow.countryName,
  779. provinceId = schRow.provinceId,
  780. provinceName = schRow.provinceName,
  781. cityId = schRow.cityId,
  782. cityName = schRow.cityName,
  783. distId = schRow.distId,
  784. distName = schRow.distName,
  785. });
  786. geoRow = geo.Where(s => s.id.Equals(geoid)).FirstOrDefault();
  787. }
  788. geoRow.hiteach += schRow.hiteach;
  789. geoRow.hiteachcc += schRow.hiteachcc;
  790. geoRow.hita += schRow.hita;
  791. geoRow.ies5 += schRow.ies5;
  792. geoRow.account += schRow.account;
  793. geoRow.sokrates += schRow.sokrates;
  794. geoRow.sokapp += schRow.sokapp;
  795. geoRow.irs += schRow.irs;
  796. geoRow.tmids = geoRow.tmids.Union(schRow.tmids).ToList();
  797. }
  798. //回傳值
  799. return Ok( new { tmid, sch, geo });
  800. }
  801. //取得TMID服務授權週期
  802. public async Task<List<object>> getTMIDAuthService(CosmosClient cosmosClientCsv2, string tmid, string prodCode, bool validFlg)
  803. {
  804. long now = DateTimeOffset.UtcNow.ToUnixTimeSeconds();
  805. var qryOption = new QueryRequestOptions() { PartitionKey = new PartitionKey("servicePeriod") };
  806. string whereSql = $"c.saleClient.tmid = '{tmid}'";
  807. if (!string.IsNullOrWhiteSpace(prodCode)) whereSql += $" AND c.prodCode = '{prodCode}'";
  808. if (validFlg) whereSql += $" AND c.startDate <= {now} AND {now} <= c.endDate";
  809. string sql = $"SELECT c.id, c.prodCode, c.type, c.startDate, c.endDate, c.number, c.unit, c.aprule FROM c WHERE {whereSql}";
  810. var client = cosmosClientCsv2.GetContainer("Habb", "Auth");
  811. var result = new List<object>();
  812. await foreach (var item in client.GetItemQueryStreamIterator(queryText: sql, requestOptions: qryOption))
  813. {
  814. var json = await JsonDocument.ParseAsync(item.ContentStream);
  815. if (json.RootElement.TryGetProperty("_count", out JsonElement count) && count.GetUInt16() > 0)
  816. {
  817. foreach (var obj in json.RootElement.GetProperty("Documents").EnumerateArray())
  818. {
  819. result.Add(
  820. new
  821. {
  822. id = obj.GetProperty("id").GetString(),
  823. prodCode = obj.GetProperty("prodCode").GetString(),
  824. type = obj.GetProperty("type").GetInt32(),
  825. startDate = obj.GetProperty("startDate").GetInt64(),
  826. endDate = obj.GetProperty("endDate").GetInt64(),
  827. number = obj.GetProperty("number").GetInt32(),
  828. aprule = obj.GetProperty("aprule")
  829. });
  830. }
  831. }
  832. }
  833. return result;
  834. }
  835. //取得TMID購買紀錄
  836. public async Task<List<Ies5OrderHis>> getTMIDAuthOrder(CosmosClient cosmosClientCsv2, string tmid, string prodCode)
  837. {
  838. SortedDictionary<string, Ies5OrderHis> OrderDic = new SortedDictionary<string, Ies5OrderHis>();
  839. var qryOption = new QueryRequestOptions() { PartitionKey = new PartitionKey("order") };
  840. //服務 ※序號、硬體 不列入購買紀錄
  841. string strQueryV = $"SELECT * FROM c WHERE IS_DEFINED(c.saleClient.tmid) AND c.saleClient.tmid = '{tmid}' AND c.prodType = 'service'";
  842. if (!string.IsNullOrWhiteSpace(prodCode))
  843. {
  844. strQueryV += $" AND c.prodCode = '{prodCode}'";
  845. }
  846. await foreach (OrderHisService orderService in cosmosClientCsv2.GetContainer("Habb", "Auth").GetItemQueryIterator<OrderHisService>(strQueryV, null, qryOption))
  847. {
  848. Ies5OrderHisService ies5OrderVRow = new Ies5OrderHisService();
  849. ies5OrderVRow.prodCode = orderService.prodCode;
  850. ies5OrderVRow.type = (orderService.contract.Equals(1)) ? "C" : (orderService.contract.Equals(2)) ? "G" : "N"; //授權方式 [FROM]contract契約型態 0:新約 1:續約 2:變更 [TO]type N:新約 C:續約 G:變更
  851. ies5OrderVRow.sdate = orderService.startDate;
  852. ies5OrderVRow.edate = orderService.endDate;
  853. ies5OrderVRow.number = orderService.number;
  854. ies5OrderVRow.unit = orderService.unit;
  855. //加入Order字典
  856. string OrderID = orderService.orderinfo.orderid.ToString();
  857. long OrderDate = orderService.orderinfo.createDate;
  858. if (OrderDic.ContainsKey(OrderID))
  859. {
  860. OrderDic[OrderID].service.Add(ies5OrderVRow);
  861. }
  862. else
  863. {
  864. Ies5OrderHis ies5OrderHisNew = new Ies5OrderHis();
  865. ies5OrderHisNew.id = OrderID;
  866. ies5OrderHisNew.date = OrderDate;
  867. ies5OrderHisNew.service.Add(ies5OrderVRow);
  868. OrderDic.Add(OrderID, ies5OrderHisNew);
  869. }
  870. }
  871. //輸出項
  872. List<Ies5OrderHis> Result = new List<Ies5OrderHis>();
  873. foreach (KeyValuePair<string, Ies5OrderHis> item in OrderDic)
  874. {
  875. Result.Add(item.Value);
  876. }
  877. return Result;
  878. }
  879. //Tool
  880. //資料遮罩
  881. ///規則:
  882. ///1.手機、電話:隱藏後4碼
  883. ///2.Mail:隱藏@前資料
  884. ///3.姓名:只顯示第一個字元
  885. ///4.身分證、護照:隱藏後4碼
  886. public string GenDataMask(string data, string type)
  887. {
  888. int length = 0;
  889. string subString = string.Empty;
  890. if (!string.IsNullOrWhiteSpace(data))
  891. {
  892. switch (type)
  893. {
  894. case "mobile":
  895. case "id":
  896. length = (data.Length > 4) ? 4 : data.Length;
  897. subString = data.Substring(data.Length - length, length);
  898. data = data.Replace(subString, "****");
  899. break;
  900. case "mail":
  901. string[] dataList = data.Split("@");
  902. subString = dataList.First();
  903. data = data.Replace(subString, "****");
  904. break;
  905. case "name":
  906. length = 2;
  907. subString = data.Substring(data.Length - length, length);
  908. data = data.Replace(subString, "〇〇");
  909. break;
  910. }
  911. }
  912. return data;
  913. }
  914. public async Task<TmidAnalysisCal> getTMIDIotData(CosmosClient cosmosClientIes5, string tmid, string toolType, string dateUnit, int year, int month, int day, long from, long to)
  915. {
  916. TmidAnalysisCal result = new TmidAnalysisCal();
  917. List<string> calPropList = new List<string>() { "lessonRecord", "useIES", "useIES5Resource", "useWebIrs", "useDeviceIrs", "useHaboard", "useHita", "lessonLengMin", "lessonLeng0", "stuShow", "stuLessonLengMin", "tGreen", "tLesson", "lTypeCoop", "lTypeIact", "lTypeMis", "lTypeTst", "lTypeDif", "lTypeNone", "lessonCnt928", "lessonCntId", "lessonCntDevice", "lessonCntIdDevice", "mission", "missionFin", "item", "interact", "sendSok" }; //要計算的ProdAnalysis欄位列表
  918. string strQuery = $"SELECT * FROM c WHERE c.tmid = '{tmid}' AND c.toolType = '{toolType}'";
  919. var qryOption = new QueryRequestOptions() { PartitionKey = new PartitionKey("TmidAnalysis") };
  920. if (!string.IsNullOrWhiteSpace(dateUnit)) strQuery += $" AND c.dateUnit = '{dateUnit}'";
  921. if (year > 0) strQuery += $" AND c.year = {year}";
  922. if (month > 0) strQuery += $" AND c.month = {month}";
  923. if (day > 0) strQuery += $" AND c.day = {day}";
  924. if (from > 0) strQuery += $" AND c.dateTime >= {from}";
  925. if (to > 0) strQuery += $" AND c.dateTime <= {to}";
  926. await foreach (TmidAnalysisCosmos tmidAnalysis in cosmosClientIes5.GetContainer(Constant.TEAMModelOS, "Teacher").GetItemQueryIterator<TmidAnalysisCosmos>(strQuery, null, qryOption))
  927. {
  928. //一般項
  929. result.tmid = tmidAnalysis.tmid;
  930. result.dateUnit = tmidAnalysis.dateUnit;
  931. result.toolType = tmidAnalysis.toolType;
  932. result.date = tmidAnalysis.date;
  933. if (tmidAnalysis.year > 0) result.year = tmidAnalysis.year;
  934. if (tmidAnalysis.month > 0) result.month = tmidAnalysis.month;
  935. if (tmidAnalysis.day > 0) result.day = tmidAnalysis.day;
  936. if (result.from.Equals(0) || tmidAnalysis.dateTime <= result.from) result.from = tmidAnalysis.dateTime;
  937. if (result.to.Equals(0) || tmidAnalysis.dateTime >= result.to) result.to = tmidAnalysis.dateTime;
  938. result.dateList.Add(tmidAnalysis.date);
  939. //統計項
  940. foreach (PropertyInfo propertyInfo in tmidAnalysis.GetType().GetProperties()) //累加項目
  941. {
  942. if (calPropList.Contains(propertyInfo.Name))
  943. {
  944. var propType = propertyInfo.PropertyType;
  945. var valNow = propertyInfo.GetValue(result);
  946. var valTodo = tmidAnalysis.GetType().GetProperty(propertyInfo.Name).GetValue(tmidAnalysis);
  947. if (propType.Equals(typeof(long))) propertyInfo.SetValue(result, Convert.ToInt64(valNow.ToString(), 10) + Convert.ToInt64(valTodo.ToString(), 10));
  948. else propertyInfo.SetValue(result, Convert.ToInt32(valNow.ToString(), 10) + Convert.ToInt32(valTodo.ToString(), 10));
  949. }
  950. }
  951. result.deviceList = result.deviceList.Union(tmidAnalysis.deviceList).ToList();
  952. result.deviceCnt = result.deviceList.Count;
  953. result.deviceNoAuthList = result.deviceNoAuthList.Union(tmidAnalysis.deviceNoAuthList).ToList();
  954. result.deviceNoAuth = result.deviceNoAuthList.Count;
  955. result.deviceAuthList = result.deviceAuthList.Union(tmidAnalysis.deviceAuthList).ToList();
  956. result.deviceAuth = result.deviceAuthList.Count;
  957. result.tmidList = result.tmidList.Union(tmidAnalysis.tmidList).ToList();
  958. result.tmidCnt = result.tmidList.Count;
  959. result.verList = result.verList.Union(tmidAnalysis.verList).ToList();
  960. }
  961. if (string.IsNullOrWhiteSpace(result.tmid)) result = null;
  962. return result;
  963. }
  964. //Model
  965. //TMID統計 基本資訊
  966. private class TmidStics
  967. {
  968. public string id { get; set; }
  969. public string name { get; set; }
  970. public string picture { get; set; }
  971. public string mobile { get; set; }
  972. public string mail { get; set; }
  973. public string country { get; set; }
  974. public string province { get; set; }
  975. public string city { get; set; }
  976. public string dist { get; set; }
  977. public string schoolCode { get; set; }
  978. public string schoolCodeW { get; set; }
  979. public string lang { get; set; }
  980. public string unitType { get; set; } //1:基礎教育機構(K-小學) 2:中等教育機構(國中、高中/職) 3:高等教育機構 4:政府單位機構 5:NGO機構 6:企業機構 7:其他 8:學前教育 9:特殊教育
  981. public string unitName { get; set; }
  982. public string jobTitle { get; set; }
  983. public TmidSticsIes5 ies5 { get; set; } //IES統計資料
  984. public TmidPoints points { get; set; }
  985. public TmidSokrates sokrates { get; set; }
  986. public TmidBenefits benefits { get; set; }
  987. public List<TmidCoupon> coupons { get; set; } = new();
  988. public List<TmidLoginTime> login { get; set; } = new();
  989. public List<object> prod { get; set; } = new();
  990. public TmidIot iot { get; set; }
  991. public bool wechat { get; set; }
  992. public bool facebook { get; set; }
  993. public bool google { get; set; }
  994. public bool ding { get; set; }
  995. public bool apple { get; set; }
  996. public bool educloudtw { get; set; }
  997. public long ts { get; set; } //資料最終變更時間
  998. public string schoolId { get; set; } //歸屬學校資訊
  999. }
  1000. //TMID統計 IES5資訊
  1001. private class TmidSticsIes5
  1002. {
  1003. public long usedSize { get; set; } //已使用的存儲空間(Byte)
  1004. public long teachSize { get; set; } //分配給教師的空間(Byte)
  1005. public long totalSize { get; set; } //總空間(Byte)
  1006. public long surplusSize { get; set; } //剩余的空間(Byte)
  1007. public List<TmidSticsIes5School> schools { get; set; } = new(); //各學校資料
  1008. public int resCount { get; set; } //教材數
  1009. public int itemCount { get; set; } //題目數
  1010. public int paperCount { get; set; } //試卷數
  1011. }
  1012. private class TmidSticsIes5School
  1013. {
  1014. public string schoolId { get; set; }
  1015. public string name { get; set; }
  1016. public string region { get; set; }
  1017. public string province { get; set; }
  1018. public string city { get; set; }
  1019. public string dist { get; set; }
  1020. public object size { get; set; } = new();
  1021. public int courseCnt { get; set; }
  1022. public bool defaultSchool { get; set; }
  1023. }
  1024. private class TmidPoints
  1025. {
  1026. public int points { get; set; } = 0; //累積的點數(只加不減)
  1027. public int balance { get; set; } = 0; //可用的點數
  1028. public int level { get; set; } = 0; //等級
  1029. }
  1030. private class TmidCoupon
  1031. {
  1032. public string code { get; set; }
  1033. public long exchange { get; set; }
  1034. }
  1035. private class TmidLoginTime
  1036. {
  1037. public string product { get; set; }
  1038. public long time { get; set; }
  1039. }
  1040. private class TmidSokrates
  1041. {
  1042. public object hiteach_data { get; set; }
  1043. public object user_channels { get; set; }
  1044. }
  1045. public class TmidSokratesHiteach
  1046. {
  1047. public TmidSokratesHiteachItem total { get; set; }
  1048. public TmidSokratesHiteachItem this_year { get; set; }
  1049. public TmidSokratesHiteachItem this_month { get; set; }
  1050. public TmidSokratesHiteachItem this_week { get; set; }
  1051. }
  1052. public class TmidSokratesHiteachItem
  1053. {
  1054. public string start_date { get; set; } = "";
  1055. public string end_date { get; set; } = "";
  1056. public string t_data { get; set; } = "0";
  1057. public string duration { get; set; } = "0";
  1058. public string attendance { get; set; } = "0";
  1059. public string interaction { get; set; } = "0";
  1060. public string t_green { get; set; } = "0";
  1061. public string learning_duration { get; set; } = "0";
  1062. public string all_total { get; set; } = "0";
  1063. public string double_green_light { get; set; } = "0";
  1064. public string public_count { get; set; } = "0";
  1065. public string material_count { get; set; } = "0";
  1066. public string personal_comment { get; set; } = "0";
  1067. public string comment_count { get; set; } = "0";
  1068. public string watchHistory { get; set; } = "0";
  1069. public string sokrates_summary { get; set; } = "0";
  1070. }
  1071. private class TmidBenefits
  1072. {
  1073. public List<object> hiteach { get; set; }
  1074. public List<object> hiteachcc { get; set; }
  1075. }
  1076. private class TmidIot
  1077. {
  1078. public TmidIotYMD hiteach { get; set; } = new();
  1079. public TmidIotYMD hiteachcc { get; set; } = new();
  1080. }
  1081. private class TmidIotYMD
  1082. {
  1083. public TmidAnalysisCal year { get; set; }
  1084. public TmidAnalysisCal month { get; set; }
  1085. public TmidAnalysisCal day { get; set; }
  1086. }
  1087. private class TmidIotSchool
  1088. {
  1089. public string id { get; set; }
  1090. public string name { get; set; }
  1091. public string country { get; set; }
  1092. public string province { get; set; }
  1093. public string city { get; set; }
  1094. public string dist { get; set; }
  1095. }
  1096. private class TmidIotProdCnt
  1097. {
  1098. public string id { get; set; }
  1099. public string name { get; set; }
  1100. public string schid { get; set; }
  1101. public int hiteach { get; set; }
  1102. public int hiteachcc { get; set; }
  1103. public int hita { get; set; }
  1104. public int ies5 { get; set; }
  1105. public int account { get; set; }
  1106. public int sokrates { get; set; }
  1107. public int sokapp { get; set; }
  1108. public int irs { get; set; }
  1109. }
  1110. private class TmidIotProdCntGeo : TmidIotProdCnt
  1111. {
  1112. public string countryId { get; set; }
  1113. public string countryName { get; set; }
  1114. public string provinceId { get; set; }
  1115. public string provinceName { get; set; }
  1116. public string cityName { get; set; }
  1117. public string cityId { get; set; }
  1118. public string distId { get; set; }
  1119. public string distName { get; set; }
  1120. public List<string> tmids { get; set; } = new();
  1121. }
  1122. //[API輸出] 訂單履歷
  1123. public class Ies5OrderHis
  1124. {
  1125. public Ies5OrderHis()
  1126. {
  1127. service = new List<Ies5OrderHisService>();
  1128. }
  1129. public string id { get; set; }
  1130. public long date { get; set; }
  1131. public List<Ies5OrderHisService> service { get; set; } = new ();
  1132. }
  1133. //[API輸出] 訂單履歷.服務型產品
  1134. public class Ies5OrderHisService
  1135. {
  1136. public string prodCode { get; set; } //產品代碼
  1137. public string type { get; set; } //授權方式 N:新約 C:續約
  1138. public long sdate { get; set; } //授權起始時間
  1139. public long edate { get; set; } //授權終止時間
  1140. public int number { get; set; } //購買數量
  1141. public string unit { get; set; } //購買單位 無單位者為null
  1142. }
  1143. //[承接DB] DB: Habb.Auth
  1144. //[承接DB] 訂單履歷基本Class
  1145. public class OrderHisBase
  1146. {
  1147. public string id { get; set; } //OPID
  1148. public OrderHisOrderInfo orderinfo { get; set; } //訂單資訊
  1149. public string prodCode { get; set; } //產品代碼
  1150. public SerialSaleClient saleClient { get; set; } = null; //銷售終端
  1151. public string prodType { get; set; } //產品類型 serial:序號 service:服務 hard:硬體
  1152. public string dataType { get; set; } //資料類型
  1153. public long operationTime { get; set; } //最新資料變更時間戳記
  1154. public int ttl { get; set; } = -1; //過期刪除秒數
  1155. }
  1156. //[承接DB] 訂單履歷基本Class.銷售終端
  1157. public class SerialSaleClient
  1158. {
  1159. public string name { get; set; } //[String]銷售終端姓名
  1160. public string schoolCode { get; set; } = null; //[String]銷售終端學校代碼
  1161. public string clientId { get; set; } = null; //[String]銷售終端客戶ID
  1162. public string tmid { get; set; } = null; //[String]TMID
  1163. public string type { get; set; } //[String]銷售終端資料類型 school:學校 client:經銷商客戶
  1164. public string countryId { get; set; } //[String]國家代碼
  1165. public string provinceId { get; set; } //[String]省代碼
  1166. public string cityId { get; set; } //[String]市代碼
  1167. public string schoolShortCode { get; set; } //[String]學校簡碼
  1168. public string districtId { get; set; } //[String]區代碼
  1169. public string dataCenter { get; set; } //[String]數據中心
  1170. }
  1171. //[承接DB] 訂單履歷基本Class.訂單資訊
  1172. public class OrderHisOrderInfo
  1173. {
  1174. public string orderid { get; set; } //[String]訂單編號
  1175. public int orderAudit { get; set; } //[Int]訂單審核狀態 0:待審, 1:通過, 2:否決, 3:問題
  1176. public int orderProperty { get; set; } //[Int]訂單類型 0:銷售,1:展示申請 2:內部申請
  1177. public long createDate { get; set; } //訂單創建時間
  1178. }
  1179. //[承接DB] 訂單履歷 服務類
  1180. public class OrderHisService : OrderHisBase
  1181. {
  1182. public int type { get; set; } //[Int]授權類型 0:銷售 1:試用
  1183. public int contract { get; set; } //[Int]契約型態 0:新約 1:續約
  1184. public long startDate { get; set; } //[long]授權起始日期 Timestamp(UTC)
  1185. public long endDate { get; set; } //[long]授權終止日期 Timestamp(UTC)
  1186. public int number { get; set; } //[Int]數量
  1187. public string unit { get; set; } //[String]單位 (無者為空)
  1188. }
  1189. //雲端服務ClientId列表
  1190. private readonly Dictionary<string, string> _dicClientIds = new()
  1191. {
  1192. { "917d02f2-5b91-404e-a71d-7bdd926ddd81", "HiTeach" },
  1193. { "c5328340-b8eb-4489-8650-8c8862d7acfe", "HiTeach" },
  1194. { "118d2d4d-32a3-44c0-808a-792ca73d3706", "HiTeachCC" },
  1195. { "227082f4-8db0-4517-b281-93dba9428bc7", "HiTeachCC" },
  1196. { "371a99aa-7efd-4c3a-90a8-95b7db4f42c0", "HiTA" },
  1197. { "0ed38cde-e7fe-4fa6-9eaa-33ad404eb532", "HiTA" },
  1198. { "531fecd1-b1a5-469a-93ca-7984e1d392f2", "IES5" },
  1199. { "c7317f88-7cea-4e48-ac57-a16071f7b884", "IES5" },
  1200. { "c8de822b-3fcf-4831-adba-cdd14653bf7f", "Account" },
  1201. { "516148eb-6a38-4657-ba98-a3699061937f", "Account" },
  1202. { "d7193896-9b12-4046-ad44-c991dd48cc39", "Sokrates" },
  1203. { "59009e38-6e30-4116-814b-7605939edc47", "Sokrates" },
  1204. { "626c7285-15aa-4abe-8c0d-2c5ff1381538", "SokAPP" },
  1205. { "5bcc16a5-7d20-4cc4-b5c2-8ed0416f32b6", "SokAPP" },
  1206. { "044482b5-d024-4f23-9b55-906884243405", "IRS" },
  1207. { "5e27b7e3-b36c-4ce9-b838-e94fd0cea080", "IRS" }
  1208. };
  1209. //TMID IOT date
  1210. public class tmidIotDate
  1211. {
  1212. public string dateUnit { get; set; } //[string]日期單位:年月日 year, month, day
  1213. public int year { get; set; } //[Int]年
  1214. public int month { get; set; } //[Int]月
  1215. public int day { get; set; } //[Int]日
  1216. public long from { get; set; } //[long]UTC timestamp 起始日
  1217. public long to { get; set; } //[long]UTC timestamp 終止日
  1218. }
  1219. public class TmidAnalysisCal : TmidAnalysisCosmos
  1220. {
  1221. public long from { get; set; } //[long]UTC timestamp 起始日
  1222. public long to { get; set; } //[long]UTC timestamp 終止日
  1223. public List<string> dateList { get; set; } = new();
  1224. }
  1225. }
  1226. }