JointEventController.cs 96 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640
  1. using Microsoft.AspNetCore.Hosting;
  2. using Microsoft.AspNetCore.Http;
  3. using Microsoft.AspNetCore.Mvc;
  4. using Microsoft.Extensions.Configuration;
  5. using TEAMModelOS.SDK.DI;
  6. using TEAMModelOS.SDK;
  7. using Microsoft.Extensions.Options;
  8. using TEAMModelOS.Models;
  9. using System.Net.Http;
  10. using Microsoft.AspNetCore.Authorization;
  11. using TEAMModelOS.Filter;
  12. using System.Text.Json;
  13. using System.Threading.Tasks;
  14. using TEAMModelOS.SDK.Extension;
  15. using System.Text;
  16. using System.Collections.Generic;
  17. using TEAMModelOS.SDK.Models;
  18. using Microsoft.Azure.Cosmos;
  19. using System;
  20. using static TEAMModelOS.SDK.Models.JointEvent;
  21. using System.Linq;
  22. using TEAMModelOS.SDK.Models.Service;
  23. using Azure.Messaging.ServiceBus;
  24. using Azure.Storage.Sas;
  25. using System.IdentityModel.Tokens.Jwt;
  26. using TEAMModelOS.Controllers.Core;
  27. namespace TEAMModelOS.Controllers.Common
  28. {
  29. [ProducesResponseType(StatusCodes.Status200OK)]
  30. [ProducesResponseType(StatusCodes.Status400BadRequest)]
  31. [Route("joint")]
  32. [ApiController]
  33. public class JointEventController : ControllerBase
  34. {
  35. private readonly AzureCosmosFactory _azureCosmos;
  36. private readonly SnowflakeId _snowflakeId;
  37. private readonly AzureServiceBusFactory _serviceBus;
  38. private readonly DingDing _dingDing;
  39. private readonly Option _option;
  40. private readonly AzureStorageFactory _azureStorage;
  41. private readonly AzureRedisFactory _azureRedis;
  42. private readonly IHttpClientFactory _httpClient;
  43. public IConfiguration _configuration { get; set; }
  44. private readonly CoreAPIHttpService _coreAPIHttpService;
  45. private readonly IWebHostEnvironment _environment;
  46. public JointEventController(IWebHostEnvironment environment, CoreAPIHttpService coreAPIHttpService, AzureCosmosFactory azureCosmos, AzureServiceBusFactory serviceBus, SnowflakeId snowflakeId, DingDing dingDing,
  47. IOptionsSnapshot<Option> option, AzureStorageFactory azureStorage, AzureRedisFactory azureRedis, IConfiguration configuration, IHttpClientFactory httpClient)
  48. {
  49. _azureCosmos = azureCosmos;
  50. _serviceBus = serviceBus;
  51. _snowflakeId = snowflakeId;
  52. _dingDing = dingDing;
  53. _option = option?.Value;
  54. _azureStorage = azureStorage;
  55. _azureRedis = azureRedis;
  56. _configuration = configuration;
  57. _coreAPIHttpService = coreAPIHttpService;
  58. _environment = environment;
  59. _httpClient = httpClient;
  60. }
  61. /// <summary>
  62. /// 取得統測活動
  63. /// </summary>
  64. [ProducesDefaultResponseType]
  65. [Authorize(Roles = "IES,HTCommunity")]
  66. [HttpPost("event/find")]
  67. public async Task<IActionResult> EventFind(JsonElement request)
  68. {
  69. try
  70. {
  71. string tmid = string.Empty;
  72. string authtoken = HttpContext.GetXAuth("AuthToken");
  73. string id_token = HttpContext.GetXAuth("IdToken");
  74. if (!string.IsNullOrEmpty(authtoken))
  75. {
  76. var jwt = new JwtSecurityToken(authtoken);
  77. tmid = jwt.Payload.Sub;
  78. }
  79. else if(!string.IsNullOrEmpty(id_token))
  80. {
  81. var jwt = new JwtSecurityToken(id_token);
  82. if (jwt.Payload.Iss.Equals("account.teammodel", StringComparison.OrdinalIgnoreCase))
  83. {
  84. tmid = jwt.Payload.Sub;
  85. }
  86. }
  87. if(string.IsNullOrWhiteSpace(tmid)) return BadRequest();
  88. long now = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds();
  89. var client = _azureCosmos.GetCosmosClient();
  90. StringBuilder stringBuilderSelect = new($"SELECT * FROM c WHERE 1=1 ");
  91. StringBuilder stringBuilderWhere = new();
  92. string jointEventId = (request.TryGetProperty("jointEventId", out JsonElement _jointEventId)) ? _jointEventId.ToString() : string.Empty;
  93. if (!string.IsNullOrWhiteSpace(jointEventId))
  94. {
  95. stringBuilderWhere.Append($" AND c.id = '{jointEventId}' ");
  96. }
  97. string creatorId = (request.TryGetProperty("creatorId", out JsonElement _creatorId)) ? _creatorId.ToString() : string.Empty;
  98. if (!string.IsNullOrWhiteSpace(creatorId))
  99. {
  100. stringBuilderWhere.Append($" AND c.creatorId = '{creatorId}' ");
  101. }
  102. if (request.TryGetProperty("name", out JsonElement name) && !string.IsNullOrWhiteSpace($"{name}"))
  103. {
  104. stringBuilderWhere.Append($" AND Contains( c.name , '{name}') = true ");
  105. }
  106. if (request.TryGetProperty("progress", out JsonElement progress) && !string.IsNullOrWhiteSpace($"{progress}"))
  107. {
  108. stringBuilderWhere.Append($" AND c.progress = '{progress}' ");
  109. }
  110. if (request.TryGetProperty("geo", out JsonElement _geo))
  111. {
  112. JointEventGeo geo = _geo.ToObject<JointEventGeo>();
  113. //國級活動
  114. string whereCountry = string.Empty;
  115. if (!string.IsNullOrWhiteSpace(geo.countryId)) {
  116. whereCountry = $" (c.geo.countryId = '{geo.countryId}' AND ( IS_NULL(c.geo.provinceId) OR c.geo.provinceId = '' ) AND ( IS_NULL(c.geo.cityId) OR c.geo.cityId = '' ) AND ( IS_NULL(c.geo.distId) OR c.geo.distId = '' ) )";
  117. }
  118. //省級活動
  119. string whereProvince = string.Empty;
  120. if (!string.IsNullOrWhiteSpace(geo.provinceId))
  121. {
  122. whereProvince = $" (c.geo.countryId = '{geo.countryId}' AND c.geo.provinceId = '{geo.provinceId}' AND ( IS_NULL(c.geo.cityId) OR c.geo.cityId = '' ) AND ( IS_NULL(c.geo.distId) OR c.geo.distId = '' ) ) ";
  123. }
  124. //市級活動
  125. string whereCity = string.Empty;
  126. if (!string.IsNullOrWhiteSpace(geo.cityId))
  127. {
  128. if(!string.IsNullOrWhiteSpace(geo.provinceId))
  129. {
  130. whereCity = $"( c.geo.countryId = '{geo.countryId}' AND c.geo.provinceId = '{geo.provinceId}' AND c.geo.cityId = '{geo.cityId}' AND ( IS_NULL(c.geo.distId) OR c.geo.distId = '' ) )";
  131. }
  132. else
  133. {
  134. whereCity = $"( c.geo.countryId = '{geo.countryId}' AND ( IS_NULL(c.geo.provinceId) OR c.geo.provinceId = '' ) AND c.geo.cityId = '{geo.cityId}' AND ( IS_NULL(c.geo.distId) OR c.geo.distId = '' ) )";
  135. }
  136. }
  137. //區級活動
  138. string whereDist = string.Empty;
  139. if (!string.IsNullOrWhiteSpace(geo.distId))
  140. {
  141. whereDist = $"( c.geo.countryId = '{geo.countryId}' AND c.geo.provinceId = '{geo.provinceId}' AND c.geo.cityId = '{geo.cityId}' AND c.geo.distId = '{geo.distId}' )";
  142. }
  143. //SQL文整理
  144. StringBuilder whereGeo = new();
  145. if(!string.IsNullOrWhiteSpace(whereCountry))
  146. {
  147. if(whereGeo.Length > 0) whereGeo.Append(" OR ");
  148. whereGeo.Append(whereCountry);
  149. }
  150. if (!string.IsNullOrWhiteSpace(whereProvince))
  151. {
  152. if (whereGeo.Length > 0) whereGeo.Append(" OR ");
  153. whereGeo.Append(whereProvince);
  154. }
  155. if (!string.IsNullOrWhiteSpace(whereCity))
  156. {
  157. if (whereGeo.Length > 0) whereGeo.Append(" OR ");
  158. whereGeo.Append(whereCity);
  159. }
  160. if (!string.IsNullOrWhiteSpace(whereDist))
  161. {
  162. if (whereGeo.Length > 0) whereGeo.Append(" OR ");
  163. whereGeo.Append(whereDist);
  164. }
  165. if (whereGeo.Length > 0)
  166. {
  167. stringBuilderWhere.Append(" AND " + whereGeo);
  168. }
  169. }
  170. List<JointEventDto> eventInfo = new List<JointEventDto>();
  171. StringBuilder sql = stringBuilderSelect.Append(stringBuilderWhere);
  172. await foreach (var item in client.GetContainer(Constant.TEAMModelOS, "Teacher").GetItemQueryStreamIteratorSql(queryText: sql.ToString(), requestOptions: new QueryRequestOptions() { PartitionKey = new PartitionKey("JointEvent") }))
  173. {
  174. using var json = await JsonDocument.ParseAsync(item.Content);
  175. if (json.RootElement.TryGetProperty("_count", out JsonElement count) && count.GetUInt16() > 0)
  176. {
  177. foreach (var obj in json.RootElement.GetProperty("Documents").EnumerateArray())
  178. {
  179. JointEventDto jointEventDto = obj.ToObject<JointEventDto>();// blobsas
  180. var (blob_uri, blob_sas) = _azureStorage.GetBlobContainerSAS(jointEventDto.creatorId, BlobContainerSasPermissions.Read | BlobContainerSasPermissions.List );
  181. jointEventDto.blobsas = blob_sas;
  182. eventInfo.Add(jointEventDto);
  183. }
  184. }
  185. }
  186. //閱卷模式
  187. List<string> limitJointEventIds = new List<string>();
  188. if (request.TryGetProperty("isMarking", out JsonElement _isMarking) && _isMarking.GetBoolean().Equals(true))
  189. {
  190. if (!string.IsNullOrWhiteSpace(tmid))
  191. {
  192. StringBuilder stringBuilderSelectM = new($"SELECT DISTINCT VALUE c.id FROM c JOIN s IN c.schedule JOIN g IN c.groups WHERE s.startTime <= {now} AND (s.endTime + 345600000) > {now} AND s.type = 'exam' AND array_contains(g.assistants,'{tmid}') "); //閱卷結束時間為行程結束+4天
  193. StringBuilder sqlM = stringBuilderSelectM.Append(stringBuilderWhere);
  194. await foreach (string item in client.GetContainer(Constant.TEAMModelOS, Constant.Teacher).GetItemQueryIteratorSql<string>(queryText: sqlM.ToString(), requestOptions: new QueryRequestOptions() { PartitionKey = new PartitionKey($"JointEvent") }))
  195. {
  196. limitJointEventIds.Add(item);
  197. }
  198. }
  199. }
  200. if(limitJointEventIds.Any())
  201. {
  202. eventInfo = eventInfo.Where(e => limitJointEventIds.Contains(e.id)).ToList();
  203. }
  204. return Ok(new { data = eventInfo });
  205. }
  206. catch (Exception e)
  207. {
  208. //await _dingDing.SendBotMsg($"OS,{_option.Location},jointevent/find()\n{e.Message}\n{e.StackTrace}", GroupNames.醍摩豆服務運維群組);
  209. return BadRequest();
  210. }
  211. }
  212. /// <summary>
  213. /// 新增修改統測活動
  214. /// </summary>
  215. [ProducesDefaultResponseType]
  216. [Authorize(Roles = "IES")]
  217. #if !DEBUG
  218. [AuthToken(Roles = "teacher")]
  219. #endif
  220. [HttpPost("event/upsert")]
  221. public async Task<IActionResult> EventUpsert(JsonElement request)
  222. {
  223. try
  224. {
  225. var client = _azureCosmos.GetCosmosClient();
  226. long now = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds();
  227. string creatorId = (request.TryGetProperty("creatorId", out JsonElement _creatorId)) ? _creatorId.ToString() : string.Empty;
  228. string name = (request.TryGetProperty("name", out JsonElement _name)) ? _name.ToString() : string.Empty;
  229. string jointEventId = (request.TryGetProperty("jointEventId", out JsonElement _jointEventId)) ? _jointEventId.ToString() : string.Empty;
  230. JointEventGeo geo = (request.TryGetProperty("geo", out JsonElement _geo)) ? _geo.ToObject<JointEventGeo>() : null;
  231. List<string> admin = (request.TryGetProperty("admin", out JsonElement _admin)) ? _admin.ToObject<List<string>>() : new List<string>();
  232. long startTime = (request.TryGetProperty("startTime", out JsonElement _startTime)) ? _startTime.GetInt64() : 0;
  233. long endTime = (request.TryGetProperty("endTime", out JsonElement _endTime)) ? _endTime.GetInt64() : 0;
  234. //新建統測活動 輸入項檢測
  235. if (string.IsNullOrWhiteSpace(creatorId) || string.IsNullOrWhiteSpace(name) || startTime.Equals(0) || endTime.Equals(0))
  236. {
  237. return Ok(new { errCode = "1", err = "Invalid param." });
  238. }
  239. if (startTime > endTime)
  240. {
  241. return Ok(new { errCode = "2", err = "Invalid startTime or endTime." });
  242. }
  243. JointEvent jointEvent = new JointEvent();
  244. if (string.IsNullOrWhiteSpace(jointEventId))
  245. {
  246. jointEvent.id = Guid.NewGuid().ToString();
  247. jointEvent.creatorId = creatorId;
  248. jointEvent.code = "JointEvent";
  249. jointEvent.createTime = now;
  250. }
  251. else
  252. {
  253. jointEvent = await client.GetContainer(Constant.TEAMModelOS, "Teacher").ReadItemAsync<JointEvent>(jointEventId, new PartitionKey("JointEvent"));
  254. if (jointEvent == null)
  255. {
  256. return Ok(new { errCode = "3", err = "Invalid jointEventId." });
  257. }
  258. }
  259. jointEvent.name = name;
  260. jointEvent.startTime = startTime;
  261. jointEvent.endTime = endTime;
  262. jointEvent.progress = (startTime <= now && now <= endTime) ? "going" : (now < startTime) ? "pending" : "finish";
  263. if (geo != null) jointEvent.geo = geo;
  264. if (admin.Count > 0) jointEvent.admin = admin;
  265. await client.GetContainer(Constant.TEAMModelOS, "Teacher").UpsertItemAsync<JointEvent>(jointEvent, new PartitionKey("JointEvent"));
  266. return Ok(new { errCode = "", err = "", jointEvent });
  267. }
  268. catch (Exception e)
  269. {
  270. //await _dingDing.SendBotMsg($"OS,{_option.Location},jointevent/find()\n{e.Message}\n{e.StackTrace}", GroupNames.醍摩豆服務運維群組);
  271. return BadRequest();
  272. }
  273. }
  274. /// <summary>
  275. /// 新增修改統測活動分組
  276. /// </summary>
  277. [ProducesDefaultResponseType]
  278. [Authorize(Roles = "IES")]
  279. #if !DEBUG
  280. [AuthToken(Roles = "teacher")]
  281. #endif
  282. [HttpPost("event-group/upsert")]
  283. public async Task<IActionResult> EventGroupUpsert(JsonElement request)
  284. {
  285. try
  286. {
  287. string errCode = string.Empty;
  288. string err = string.Empty;
  289. var client = _azureCosmos.GetCosmosClient();
  290. string jointEventId = (request.TryGetProperty("jointEventId", out JsonElement _jointEventId)) ? _jointEventId.ToString() : string.Empty;
  291. if (string.IsNullOrWhiteSpace(jointEventId)) return BadRequest();
  292. List<JointEventGroup> groups = (request.TryGetProperty("groups", out JsonElement _groups)) ? _groups.ToObject<List<JointEventGroup>>() : new List<JointEventGroup>();
  293. if (groups.Count.Equals(0)) return BadRequest();
  294. JointEvent jointEvent = await client.GetContainer(Constant.TEAMModelOS, "Teacher").ReadItemAsync<JointEvent>(jointEventId, new PartitionKey("JointEvent"));
  295. if (jointEvent == null)
  296. {
  297. return Ok(new { errCode = "3", err = "Invalid jointEventId." });
  298. }
  299. foreach (JointEventGroup groupRow in groups)
  300. {
  301. if (!string.IsNullOrWhiteSpace(groupRow.id))
  302. {
  303. JointEventGroup groupNow = jointEvent.groups.Where(g => g.id.Equals(groupRow.id)).FirstOrDefault();
  304. if (groupNow == null)
  305. {
  306. errCode = "4";
  307. err += $"Invalid groupId:{groupRow.id}. \r\n";
  308. continue;
  309. }
  310. groupNow.name = groupRow.name;
  311. groupNow.assistants = groupRow.assistants.Where(a => !string.IsNullOrWhiteSpace(a)).ToHashSet();
  312. }
  313. else
  314. {
  315. JointEventGroup groupNow = new JointEventGroup();
  316. groupNow.id = Guid.NewGuid().ToString();
  317. groupNow.name = groupRow.name;
  318. groupNow.assistants = groupRow.assistants.Where(a => !string.IsNullOrWhiteSpace(a)).ToHashSet();
  319. jointEvent.groups.Add(groupNow);
  320. }
  321. }
  322. await client.GetContainer(Constant.TEAMModelOS, "Teacher").ReplaceItemAsync<JointEvent>(jointEvent, jointEvent.id, new PartitionKey("JointEvent"));
  323. return Ok(new { errCode, err, jointEvent });
  324. }
  325. catch (Exception e)
  326. {
  327. //await _dingDing.SendBotMsg($"OS,{_option.Location},jointevent/find()\n{e.Message}\n{e.StackTrace}", GroupNames.醍摩豆服務運維群組);
  328. return BadRequest();
  329. }
  330. }
  331. /// <summary>
  332. /// 刪除統測活動分組
  333. /// </summary>
  334. [ProducesDefaultResponseType]
  335. [Authorize(Roles = "IES")]
  336. #if !DEBUG
  337. [AuthToken(Roles = "teacher")]
  338. #endif
  339. [HttpPost("event-group/delete")]
  340. public async Task<IActionResult> EventGroupDelete(JsonElement request)
  341. {
  342. var client = _azureCosmos.GetCosmosClient();
  343. string jointEventId = (request.TryGetProperty("jointEventId", out JsonElement _jointEventId)) ? _jointEventId.ToString() : string.Empty;
  344. if (string.IsNullOrWhiteSpace(jointEventId)) return BadRequest();
  345. string groupId = (request.TryGetProperty("groupId", out JsonElement _groupId)) ? _groupId.GetString() : string.Empty;
  346. if (string.IsNullOrWhiteSpace(groupId)) return BadRequest();
  347. JointEvent jointEvent = await client.GetContainer(Constant.TEAMModelOS, "Teacher").ReadItemAsync<JointEvent>(jointEventId, new PartitionKey("JointEvent"));
  348. if (jointEvent == null)
  349. {
  350. return Ok(new { errCode = "3", err = "Invalid jointEventId." });
  351. }
  352. JointEventGroup group = jointEvent.groups.Where(g => g.id.Equals(groupId)).FirstOrDefault();
  353. if (group == null)
  354. {
  355. return Ok(new { errCode = "4", err = "Invalid groupId." });
  356. }
  357. jointEvent.groups.Remove(group);
  358. await client.GetContainer(Constant.TEAMModelOS, "Teacher").ReplaceItemAsync<JointEvent>(jointEvent, jointEvent.id, new PartitionKey("JointEvent"));
  359. return Ok(new { errCode = "", err = "", jointEvent });
  360. }
  361. /// <summary>
  362. /// 新增修改統測行程
  363. /// </summary>
  364. [ProducesDefaultResponseType]
  365. [Authorize(Roles = "IES")]
  366. #if !DEBUG
  367. [AuthToken(Roles = "teacher")]
  368. #endif
  369. [HttpPost("schedule/upsert")]
  370. public async Task<IActionResult> EventScheduleUpsert(JsonElement request)
  371. {
  372. try
  373. {
  374. string errCode = string.Empty;
  375. string err = string.Empty;
  376. var client = _azureCosmos.GetCosmosClient();
  377. long now = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds();
  378. string jointEventId = (request.TryGetProperty("jointEventId", out JsonElement _jointEventId)) ? _jointEventId.ToString() : string.Empty;
  379. if (string.IsNullOrWhiteSpace(jointEventId)) return BadRequest();
  380. List<JointEventSchedule> schedules = (request.TryGetProperty("schedules", out JsonElement _schedules)) ? _schedules.ToObject<List<JointEventSchedule>>() : new List<JointEventSchedule>();
  381. if (schedules.Count.Equals(0)) return BadRequest();
  382. var sbm = new List<ServiceBusMessage>(); //ServiceBus訊息列表
  383. //取得活動本體
  384. JointEvent jointEvent = await client.GetContainer(Constant.TEAMModelOS, "Teacher").ReadItemAsync<JointEvent>(jointEventId, new PartitionKey("JointEvent"));
  385. if (jointEvent == null)
  386. {
  387. return Ok(new { errCode = "3", err = "Invalid jointEventId." });
  388. }
  389. //取得活動Schedule
  390. foreach (JointEventSchedule schedule in schedules)
  391. {
  392. string mode = "add";
  393. JointEventSchedule jointEventSchedule = new JointEventSchedule();
  394. if (!string.IsNullOrWhiteSpace(schedule.id))
  395. {
  396. jointEventSchedule = jointEvent.schedule.Where(s => s.id.Equals(schedule.id)).FirstOrDefault();
  397. if (jointEventSchedule == null)
  398. {
  399. errCode = "5";
  400. err += $"Invalid scheduleId:{schedule.id}. \r\n";
  401. continue;
  402. }
  403. if (jointEventSchedule.progress.Equals("going"))
  404. {
  405. errCode = "10";
  406. err += $"JointSchedule (id:{schedule.id}) is going:, can't be changed. \r\n";
  407. continue;
  408. }
  409. mode = "upd";
  410. }
  411. else
  412. {
  413. jointEventSchedule.id = Guid.NewGuid().ToString();
  414. }
  415. jointEventSchedule.name = schedule.name;
  416. jointEventSchedule.type = schedule.type;
  417. jointEventSchedule.examType = (schedule.type.Equals("join")) ? null : schedule.examType;
  418. jointEventSchedule.examOverwrite = schedule.examOverwrite;
  419. long startTimeOld = jointEventSchedule.startTime;
  420. long endTimeOld = jointEventSchedule.endTime;
  421. jointEventSchedule.startTime = schedule.startTime;
  422. jointEventSchedule.endTime = schedule.endTime;
  423. jointEventSchedule.progress = (schedule.startTime <= now && now <= schedule.endTime) ? "going" : (now < schedule.startTime) ? "pending" : "finish";
  424. jointEventSchedule.orderby = schedule.orderby;
  425. if (schedule.location != null) jointEventSchedule.location = schedule.location;
  426. if (schedule.description != null) jointEventSchedule.description = schedule.description;
  427. if (schedule.blobs != null) jointEventSchedule.blobs = schedule.blobs;
  428. if (mode.Equals("add"))
  429. {
  430. jointEvent.schedule.Add(jointEventSchedule);
  431. }
  432. //製作Schedule progress更新訊息(active-task)
  433. bool sendScheduleMsg = (!startTimeOld.Equals(jointEventSchedule.startTime) || !endTimeOld.Equals(jointEventSchedule.endTime) ) ? true : false; //是否發送變更Schedule.progress訊息
  434. if(sendScheduleMsg)
  435. {
  436. var msg = new ServiceBusMessage(new { jointEventId = $"{jointEvent.id}", jointScheduleId = $"{jointEventSchedule.id}", progress = jointEventSchedule.progress }.ToJsonString());
  437. msg.ApplicationProperties.Add("name", "JointEventSchedule");
  438. sbm.Add(msg);
  439. }
  440. }
  441. //排序
  442. jointEvent.schedule = jointEvent.schedule.OrderBy(s => s.orderby).ToList();
  443. int index = 0;
  444. foreach (JointEventSchedule schedule in jointEvent.schedule)
  445. {
  446. schedule.orderby = index;
  447. index++;
  448. }
  449. //DB更新:JointEvent
  450. await client.GetContainer(Constant.TEAMModelOS, "Teacher").ReplaceItemAsync<JointEvent>(jointEvent, jointEvent.id, new PartitionKey("JointEvent"));
  451. //批量發送Schedule progress消息
  452. if(sbm.Count > 0)
  453. {
  454. var s = await _serviceBus.GetServiceBusClient().SendBatchMessageAsync(_configuration.GetValue<string>("Azure:ServiceBus:ActiveTask"), sbm);
  455. }
  456. return Ok(new { errCode, err, jointEvent });
  457. }
  458. catch (Exception e)
  459. {
  460. //await _dingDing.SendBotMsg($"OS,{_option.Location},jointevent/find()\n{e.Message}\n{e.StackTrace}", GroupNames.醍摩豆服務運維群組);
  461. return BadRequest();
  462. }
  463. }
  464. /// <summary>
  465. /// 刪除統測統測行程
  466. /// </summary>
  467. [ProducesDefaultResponseType]
  468. [Authorize(Roles = "IES")]
  469. #if !DEBUG
  470. [AuthToken(Roles = "teacher")]
  471. #endif
  472. [HttpPost("schedule/delete")]
  473. public async Task<IActionResult> EventScheduleDelete(JsonElement request)
  474. {
  475. try
  476. {
  477. var client = _azureCosmos.GetCosmosClient();
  478. string jointEventId = (request.TryGetProperty("jointEventId", out JsonElement _jointEventId)) ? _jointEventId.ToString() : string.Empty;
  479. if (string.IsNullOrWhiteSpace(jointEventId)) return BadRequest();
  480. string scheduleId = (request.TryGetProperty("scheduleId", out JsonElement _scheduleId)) ? _scheduleId.ToString() : string.Empty;
  481. if (string.IsNullOrWhiteSpace(scheduleId)) return BadRequest();
  482. //取得活動本體
  483. JointEvent jointEvent = await client.GetContainer(Constant.TEAMModelOS, "Teacher").ReadItemAsync<JointEvent>(jointEventId, new PartitionKey("JointEvent"));
  484. if (jointEvent == null)
  485. {
  486. return Ok(new { errCode = "3", err = "Invalid jointEventId." });
  487. }
  488. //取得活動Schedule
  489. JointEventSchedule jointEventSchedule = jointEvent.schedule.Where(s => s.id.Equals(scheduleId)).FirstOrDefault();
  490. if (jointEventSchedule == null)
  491. {
  492. return Ok(new { errCode = "5", err = "Invalid scheduleId." });
  493. }
  494. else
  495. {
  496. jointEvent.schedule.Remove(jointEventSchedule);
  497. //排序
  498. jointEvent.schedule = jointEvent.schedule.OrderBy(s => s.orderby).ToList();
  499. int index = 0;
  500. foreach (JointEventSchedule schedule in jointEvent.schedule)
  501. {
  502. schedule.orderby = index;
  503. index++;
  504. }
  505. //DB更新:JointEvent
  506. await client.GetContainer(Constant.TEAMModelOS, "Teacher").ReplaceItemAsync<JointEvent>(jointEvent, jointEvent.id, new PartitionKey("JointEvent"));
  507. return Ok(new { errCode = "", err = "", jointEvent });
  508. }
  509. }
  510. catch (Exception e)
  511. {
  512. //await _dingDing.SendBotMsg($"OS,{_option.Location},jointevent/find()\n{e.Message}\n{e.StackTrace}", GroupNames.醍摩豆服務運維群組);
  513. return BadRequest();
  514. }
  515. }
  516. /// <summary>
  517. /// 取得統測教師課程及組別
  518. /// </summary>
  519. [ProducesDefaultResponseType]
  520. [Authorize(Roles = "IES,HTCommunity")]
  521. [HttpPost("course/find")]
  522. public async Task<IActionResult> JointCourseFind(JsonElement request)
  523. {
  524. try {
  525. var client = _azureCosmos.GetCosmosClient();
  526. List<JointEventGroupDto> JointCourseDto = new List<JointEventGroupDto>();
  527. StringBuilder stringBuilder = new($"SELECT * FROM c WHERE 1=1 ");
  528. string jointEventId = (request.TryGetProperty("jointEventId", out JsonElement _jointEventId)) ? _jointEventId.ToString() : string.Empty;
  529. string jointGroupId = (request.TryGetProperty("jointGroupId", out JsonElement _jointGroupId)) ? _jointGroupId.ToString() : string.Empty;
  530. string type = (request.TryGetProperty("type", out JsonElement _type)) ? _type.ToString() : "regular"; //預設取得報名名單
  531. if (string.IsNullOrWhiteSpace(jointEventId)) return BadRequest();
  532. //取得活動
  533. JointEvent jointEvent = await client.GetContainer(Constant.TEAMModelOS, "Teacher").ReadItemAsync<JointEvent>(jointEventId, new PartitionKey("JointEvent"));
  534. if (jointEvent == null)
  535. {
  536. return Ok(new { errCode = "3", err = "Invalid jointEventId.", data = JointCourseDto });
  537. }
  538. //取得報名課程
  539. stringBuilder.Append($" AND c.jointEventId = '{jointEventId}' ");
  540. if (!string.IsNullOrWhiteSpace(jointGroupId))
  541. {
  542. stringBuilder.Append($" AND c.jointGroupId = '{jointGroupId}' ");
  543. }
  544. string creatorId = (request.TryGetProperty("creatorId", out JsonElement _creatorId)) ? _creatorId.ToString() : string.Empty;
  545. if (!string.IsNullOrWhiteSpace(creatorId))
  546. {
  547. stringBuilder.Append($" AND c.creatorId = '{creatorId}' ");
  548. }
  549. if(type.Equals("regular"))
  550. {
  551. stringBuilder.Append($" AND (c.type = 'regular' OR NOT IS_DEFINED(c.type) OR IS_NULL(c.type)) ");
  552. }
  553. else
  554. {
  555. stringBuilder.Append($" AND c.type = '{type}' ");
  556. }
  557. List<string> groupIdList = new List<string>(); //要取得資訊的班級ID
  558. await foreach (var item in client.GetContainer(Constant.TEAMModelOS, "Teacher").GetItemQueryStreamIteratorSql(queryText: stringBuilder.ToString(), requestOptions: new QueryRequestOptions() { PartitionKey = new PartitionKey("JointCourse") }))
  559. {
  560. using var json = await JsonDocument.ParseAsync(item.Content);
  561. if (json.RootElement.TryGetProperty("_count", out JsonElement count) && count.GetUInt16() > 0)
  562. {
  563. foreach (var obj in json.RootElement.GetProperty("Documents").EnumerateArray())
  564. {
  565. JointEventGroupDto jointEventGroupDto = obj.ToObject<JointEventGroupDto>();
  566. foreach(JointEventGroupDto.JointEventGroupCourseDto course in jointEventGroupDto.courseLists)
  567. {
  568. foreach(JointEventGroupDto.JointEventGroupCourseGroupDto group in course.groupLists)
  569. {
  570. foreach (JointEventSchedule schedule in jointEvent.schedule)
  571. {
  572. string scheduleStatus = await GetGroupFinishJointSchedule(jointEventGroupDto.jointEventId, jointEventGroupDto.jointGroupId, group.id, schedule);
  573. group.schedule.Add(new {id = schedule.id, name = schedule.name, status = scheduleStatus });
  574. }
  575. if(!groupIdList.Contains(group.id))
  576. {
  577. groupIdList.Add(group.id);
  578. }
  579. }
  580. }
  581. JointCourseDto.Add(jointEventGroupDto);
  582. }
  583. }
  584. }
  585. //取得各課程名單(班級)人數
  586. Dictionary<string, int> groupStuNumDic = new Dictionary<string, int>(); //各課程名單(各班級)人數 key:班級ID val:人數
  587. StringBuilder stringBuilderGList = new($"SELECT c.id, ARRAY_LENGTH(c.members) as stuNum FROM c WHERE ARRAY_CONTAINS({JsonSerializer.Serialize(groupIdList)}, c.id) ");
  588. await foreach (var item in client.GetContainer(Constant.TEAMModelOS, "Teacher").GetItemQueryStreamIteratorSql(queryText: stringBuilderGList.ToString(), requestOptions: new QueryRequestOptions() { PartitionKey = new PartitionKey("GroupList") }))
  589. {
  590. using var json = await JsonDocument.ParseAsync(item.Content);
  591. foreach (var obj in json.RootElement.GetProperty("Documents").EnumerateArray())
  592. {
  593. string groupId = obj.GetProperty("id").GetString();
  594. int stuNum = obj.GetProperty("stuNum").GetInt32();
  595. if (!groupStuNumDic.ContainsKey(groupId))
  596. {
  597. groupStuNumDic.Add(groupId, stuNum);
  598. }
  599. }
  600. }
  601. foreach(JointEventGroupDto jointCourse in JointCourseDto)
  602. {
  603. foreach(JointEventGroupDto.JointEventGroupCourseDto cInfo in jointCourse.courseLists)
  604. {
  605. foreach (JointEventGroupDto.JointEventGroupCourseGroupDto gInfo in cInfo.groupLists)
  606. {
  607. if(groupStuNumDic.ContainsKey(gInfo.id))
  608. {
  609. gInfo.stuNum = groupStuNumDic[gInfo.id];
  610. }
  611. }
  612. }
  613. }
  614. return Ok(new { data = JointCourseDto });
  615. }
  616. catch (Exception e)
  617. {
  618. //await _dingDing.SendBotMsg($"OS,{_option.Location},jointevent/jointcourse/find()\n{e.Message}\n{e.StackTrace}", GroupNames.醍摩豆服務運維群組);
  619. return BadRequest();
  620. }
  621. }
  622. /// <summary>
  623. /// 新建修改教師報名課程及組別
  624. /// </summary>
  625. [ProducesDefaultResponseType]
  626. [Authorize(Roles = "IES,HTCommunity")]
  627. [HttpPost("course/upsert")]
  628. public async Task<IActionResult> JointCourseUpsert(JsonElement request)
  629. {
  630. try
  631. {
  632. var client = _azureCosmos.GetCosmosClient();
  633. string jointEventId = (request.TryGetProperty("jointEventId", out JsonElement _jointEventId)) ? _jointEventId.ToString() : string.Empty;
  634. string jointGroupId = (request.TryGetProperty("jointGroupId", out JsonElement _jointGroupId)) ? _jointGroupId.ToString() : string.Empty;
  635. string creatorId = (request.TryGetProperty("creatorId", out JsonElement _creatorId)) ? _creatorId.ToString() : string.Empty;
  636. string creatorName = (request.TryGetProperty("creatorName", out JsonElement _creatorName)) ? _creatorName.ToString() : string.Empty;
  637. string creatorEmail = (request.TryGetProperty("creatorEmail", out JsonElement _creatorEmail)) ? _creatorEmail.ToString() : string.Empty;
  638. string schoolId = (request.TryGetProperty("schoolId", out JsonElement _schoolId)) ? _schoolId.ToString() : string.Empty;
  639. string schoolName = (request.TryGetProperty("schoolName", out JsonElement _schoolName)) ? _schoolName.ToString() : string.Empty;
  640. string countryId = (request.TryGetProperty("countryId", out JsonElement _countryId)) ? _countryId.ToString() : string.Empty;
  641. string countryName = (request.TryGetProperty("countryName", out JsonElement _countryName)) ? _countryName.ToString() : string.Empty;
  642. string provinceId = (request.TryGetProperty("provinceId", out JsonElement _provinceId)) ? _provinceId.ToString() : string.Empty;
  643. string provinceName = (request.TryGetProperty("provinceName", out JsonElement _provinceName)) ? _provinceName.ToString() : string.Empty;
  644. string cityId = (request.TryGetProperty("cityId", out JsonElement _cityId)) ? _cityId.ToString() : string.Empty;
  645. string cityName = (request.TryGetProperty("cityName", out JsonElement _cityName)) ? _cityName.ToString() : string.Empty;
  646. string scope = (request.TryGetProperty("scope", out JsonElement _scope)) ? _scope.ToString() : string.Empty;
  647. List<JointEventGroupBase.JointEventGroupCourse> courseLists = (request.TryGetProperty("courseLists", out JsonElement _courseLists)) ? _courseLists.ToObject<List<JointEventGroupBase.JointEventGroupCourse>>() : null;
  648. if (string.IsNullOrWhiteSpace(jointEventId) || string.IsNullOrWhiteSpace(jointGroupId) || string.IsNullOrWhiteSpace(creatorId) || courseLists == null || string.IsNullOrWhiteSpace(scope))
  649. {
  650. return Ok(new { errCode = "1", err = "Invalid param." });
  651. }
  652. JointEventGroupDb jointCourse = new JointEventGroupDb();
  653. StringBuilder stringBuilder = new($"SELECT * FROM c WHERE c.jointEventId = '{jointEventId}' AND c.jointGroupId = '{jointGroupId}' AND c.creatorId = '{creatorId}' AND ( IS_DEFINED(c.type) = false OR c.type = 'regular' ) ");
  654. await foreach (var item in client.GetContainer(Constant.TEAMModelOS, "Teacher").GetItemQueryStreamIteratorSql(queryText: stringBuilder.ToString(), requestOptions: new QueryRequestOptions() { PartitionKey = new PartitionKey("JointCourse") }))
  655. {
  656. using var json = await JsonDocument.ParseAsync(item.Content);
  657. if (json.RootElement.TryGetProperty("_count", out JsonElement count) && count.GetUInt16() > 0)
  658. {
  659. foreach (var obj in json.RootElement.GetProperty("Documents").EnumerateArray())
  660. {
  661. jointCourse = obj.ToObject<JointEventGroupDb>();
  662. }
  663. }
  664. }
  665. //刪除
  666. if (!string.IsNullOrWhiteSpace(jointCourse.id) && courseLists.Count.Equals(0))
  667. {
  668. await client.GetContainer(Constant.TEAMModelOS, "Teacher").DeleteItemAsync<JointEventGroupDb>(jointCourse.id, new PartitionKey("JointCourse"));
  669. return Ok(new { errCode = "", err = "", jointCourse = new { } });
  670. }
  671. //新建修改
  672. else
  673. {
  674. //新建
  675. if (string.IsNullOrWhiteSpace(jointCourse.id))
  676. {
  677. jointCourse.scope = scope;
  678. jointCourse.id = Guid.NewGuid().ToString();
  679. jointCourse.code = "JointCourse";
  680. jointCourse.pk = "JointCourse";
  681. jointCourse.jointEventId = jointEventId;
  682. jointCourse.jointGroupId = jointGroupId;
  683. jointCourse.creatorId = creatorId;
  684. jointCourse.creatorName = creatorName;
  685. jointCourse.creatorEmail = creatorEmail;
  686. jointCourse.createTime = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds();
  687. }
  688. //修改
  689. jointCourse.schoolId = schoolId;
  690. jointCourse.schoolName = schoolName;
  691. jointCourse.countryId = countryId;
  692. jointCourse.countryName = countryName;
  693. jointCourse.provinceId = provinceId;
  694. jointCourse.provinceName = provinceName;
  695. jointCourse.cityId = cityId;
  696. jointCourse.cityName = cityName;
  697. jointCourse.type = "regular"; //教師報名名單
  698. //courseLists修正:防止course的重複生成
  699. List<JointEventGroupBase.JointEventGroupCourse> courseListsFix = new List<JointEventGroupBase.JointEventGroupCourse>();
  700. foreach (JointEventGroupBase.JointEventGroupCourse courseInfo in courseLists)
  701. {
  702. foreach (JointEventGroupBase.JointEventGroupCourseGroup groupInfo in courseInfo.groupLists)
  703. {
  704. JointEventGroupBase.JointEventGroupCourse courseFixExist = courseListsFix.Where(c => c.courseId.Equals(courseInfo.courseId)).FirstOrDefault();
  705. if (courseFixExist == null) //還不存在此course
  706. {
  707. JointEventGroupBase.JointEventGroupCourse courseAdd = new JointEventGroupBase.JointEventGroupCourse()
  708. {
  709. subjectId = courseInfo.subjectId,
  710. courseId = courseInfo.courseId,
  711. courseName = courseInfo.courseName,
  712. groupLists = new List<JointEventGroupBase.JointEventGroupCourseGroup>() { groupInfo }
  713. };
  714. courseListsFix.Add(courseAdd);
  715. }
  716. else //已存在course
  717. {
  718. JointEventGroupBase.JointEventGroupCourseGroup groupFixExist = courseFixExist.groupLists.Where(g => g.id.Equals(groupInfo.id)).FirstOrDefault();
  719. if (groupFixExist == null)
  720. {
  721. courseFixExist.groupLists.Add(groupInfo);
  722. }
  723. }
  724. }
  725. }
  726. jointCourse.courseLists = courseListsFix;
  727. await client.GetContainer(Constant.TEAMModelOS, "Teacher").UpsertItemAsync<JointEventGroupDb>(jointCourse, new PartitionKey("JointCourse"));
  728. }
  729. //取得該老師報名該活動該組別且progress="going"的JointExam並生成Exam
  730. List<JointExam> jointExams = new List<JointExam>();
  731. ///個人
  732. if (jointCourse.scope.Equals("private"))
  733. {
  734. StringBuilder sqlJointExam = new($"SELECT * FROM c WHERE c.jointEventId = '{jointCourse.jointEventId}' AND c.jointGroupId = '{jointCourse.jointGroupId}' AND c.progress = 'going' AND c.examType = 'regular' ");
  735. await foreach (var item in client.GetContainer(Constant.TEAMModelOS, "Common").GetItemQueryStreamIteratorSql(queryText: sqlJointExam.ToString(), requestOptions: new QueryRequestOptions() { PartitionKey = new PartitionKey("JointExam") }))
  736. {
  737. using var json = await JsonDocument.ParseAsync(item.Content);
  738. if (json.RootElement.TryGetProperty("_count", out JsonElement count) && count.GetUInt16() > 0)
  739. {
  740. foreach (var obj in json.RootElement.GetProperty("Documents").EnumerateArray())
  741. {
  742. jointExams.Add(obj.ToObject<JointExam>());
  743. }
  744. }
  745. }
  746. }
  747. ///學校[待做]
  748. else if (scope.Equals("school"))
  749. {
  750. }
  751. //Exam生成邏輯
  752. if (jointExams.Count > 0)
  753. {
  754. foreach (var info in jointExams)
  755. {
  756. //生成該報名教師的統測活動個人評量
  757. await JointService.GenerateExamFromJointExamAsync(client, _azureStorage, _serviceBus, _coreAPIHttpService, _azureRedis, _configuration, _dingDing, info, creatorId);
  758. }
  759. }
  760. return Ok(new { errCode = "", err = "", jointCourse });
  761. }
  762. catch (Exception e)
  763. {
  764. //await _dingDing.SendBotMsg($"OS,{_option.Location},jointevent/jointcourse/find()\n{e.Message}\n{e.StackTrace}", GroupNames.醍摩豆服務運維群組);
  765. return BadRequest();
  766. }
  767. }
  768. /// <summary>
  769. /// 刪除教師課程及組別
  770. /// </summary>
  771. [ProducesDefaultResponseType]
  772. [Authorize(Roles = "IES")]
  773. #if !DEBUG
  774. [AuthToken(Roles = "teacher")]
  775. #endif
  776. [HttpPost("course/delete")]
  777. public async Task<IActionResult> JointCourseDelete(JsonElement request)
  778. {
  779. var client = _azureCosmos.GetCosmosClient();
  780. string jointCourseId = (request.TryGetProperty("jointCourseId", out JsonElement _jointCourseId)) ? _jointCourseId.ToString() : string.Empty;
  781. if (string.IsNullOrWhiteSpace(jointCourseId))
  782. {
  783. return BadRequest();
  784. }
  785. await client.GetContainer(Constant.TEAMModelOS, "Teacher").DeleteItemAsync<JointEventGroupDb>(jointCourseId, new PartitionKey("JointCourse"));
  786. return Ok(new { errCode = "", err = "" });
  787. }
  788. /// <summary>
  789. /// 取得統測評量
  790. /// </summary>
  791. /// <param name="request"></param>
  792. /// <returns></returns>
  793. [ProducesDefaultResponseType]
  794. [Authorize(Roles = "IES,HTCommunity")]
  795. [HttpPost("exam/find")]
  796. public async Task<IActionResult> ExamFind(JsonElement request)
  797. {
  798. try
  799. {
  800. var client = _azureCosmos.GetCosmosClient();
  801. StringBuilder stringBuilder = new($"SELECT * FROM c WHERE 1=1 ");
  802. string jointEventId = (request.TryGetProperty("jointEventId", out JsonElement _jointEventId)) ? _jointEventId.ToString() : string.Empty;
  803. if (string.IsNullOrWhiteSpace(jointEventId)) return BadRequest();
  804. stringBuilder.Append($" AND c.jointEventId = '{jointEventId}' ");
  805. string creatorId = (request.TryGetProperty("creatorId", out JsonElement _creatorId)) ? _creatorId.ToString() : string.Empty;
  806. if (!string.IsNullOrWhiteSpace(creatorId))
  807. {
  808. stringBuilder.Append($" AND c.creatorId = '{creatorId}' ");
  809. }
  810. string jointScheduleId = (request.TryGetProperty("jointScheduleId", out JsonElement _jointScheduleId)) ? _jointScheduleId.ToString() : string.Empty;
  811. if (!string.IsNullOrWhiteSpace(jointScheduleId))
  812. {
  813. stringBuilder.Append($" AND c.jointScheduleId = '{jointScheduleId}' ");
  814. }
  815. string jointGroupId = (request.TryGetProperty("jointGroupId", out JsonElement _jointGroupId)) ? _jointGroupId.ToString() : string.Empty;
  816. if (!string.IsNullOrWhiteSpace(jointGroupId))
  817. {
  818. stringBuilder.Append($" AND c.jointGroupId = '{jointGroupId}' ");
  819. }
  820. if (request.TryGetProperty("stime", out JsonElement _stime))
  821. {
  822. if (long.TryParse($"{_stime}", out long stime))
  823. {
  824. stringBuilder.Append($" AND c.createTime >= {stime} ");
  825. };
  826. };
  827. if (request.TryGetProperty("etime", out JsonElement _etime))
  828. {
  829. if (long.TryParse($"{_etime}", out long etime))
  830. {
  831. stringBuilder.Append($" AND c.createTime <= {etime} ");
  832. };
  833. };
  834. if (request.TryGetProperty("name", out JsonElement name) && !string.IsNullOrWhiteSpace($"{name}"))
  835. {
  836. stringBuilder.Append($" AND Contains( c.name , '{name}') = true ");
  837. }
  838. if (request.TryGetProperty("progress", out JsonElement progress) && !string.IsNullOrWhiteSpace($"{progress}"))
  839. {
  840. stringBuilder.Append($" AND c.progress = '{progress}' ");
  841. }
  842. if (request.TryGetProperty("source", out JsonElement source) && !string.IsNullOrWhiteSpace($"{source}"))
  843. {
  844. stringBuilder.Append($" AND c.source = '{source}' ");
  845. }
  846. stringBuilder.Append("order by c.createTime desc");
  847. //string token = null;
  848. string token = default;
  849. //默认不指定返回大小
  850. int? topcout = null;
  851. if (request.TryGetProperty("count", out JsonElement jcount))
  852. {
  853. if (!jcount.ValueKind.Equals(JsonValueKind.Undefined) && !jcount.ValueKind.Equals(JsonValueKind.Null) && jcount.TryGetInt32(out int data))
  854. {
  855. topcout = data;
  856. }
  857. }
  858. //是否需要进行分页查询,默认不分页
  859. bool iscontinuation = false;
  860. if (topcout != null && topcout.Value > 0)
  861. {
  862. iscontinuation = true;
  863. }
  864. //如果指定了返回大小
  865. if (request.TryGetProperty("token", out JsonElement token_1))
  866. {
  867. if (!token_1.ValueKind.Equals(JsonValueKind.Null) && token_1.ValueKind.Equals(JsonValueKind.String))
  868. {
  869. token = token_1.GetString();
  870. }
  871. }
  872. List<JointExamIdCollector> jointExamIdCollectorsPrivate = new List<JointExamIdCollector>();
  873. List<JointExamIdCollector> jointExamIdCollectorsSchool = new List<JointExamIdCollector>();
  874. List<JointExam> jointExamInfo = new List<JointExam>();
  875. await foreach (var item in client.GetContainer(Constant.TEAMModelOS, "Common").GetItemQueryStreamIteratorSql(queryText: stringBuilder.ToString(), continuationToken: token, requestOptions: new QueryRequestOptions() { PartitionKey = new PartitionKey("JointExam"), MaxItemCount = topcout }))
  876. {
  877. using var json = await JsonDocument.ParseAsync(item.Content);
  878. if (json.RootElement.TryGetProperty("_count", out JsonElement count) && count.GetUInt16() > 0)
  879. {
  880. foreach (var obj in json.RootElement.GetProperty("Documents").EnumerateArray())
  881. {
  882. JointExam jointExamRow = obj.ToObject<JointExam>();
  883. //取得JointCourse用ID束
  884. if(jointExamRow.scope.Equals("private"))
  885. {
  886. JointExamIdCollector jointExamIdCollectorRowNow = jointExamIdCollectorsPrivate.Where(c => c.jointEventId.Equals(jointExamRow.jointEventId) && c.jointGroupId.Equals(jointExamRow.jointGroupId)).FirstOrDefault();
  887. if (jointExamIdCollectorRowNow == null)
  888. {
  889. JointExamIdCollector jointExamIdCollectorRow = new JointExamIdCollector();
  890. jointExamIdCollectorRow.jointEventId = jointExamRow.jointEventId;
  891. jointExamIdCollectorRow.jointGroupId = jointExamRow.jointGroupId;
  892. jointExamIdCollectorsPrivate.Add(jointExamIdCollectorRow);
  893. }
  894. }
  895. else
  896. {
  897. JointExamIdCollector jointExamIdCollectorRowNow = jointExamIdCollectorsSchool.Where(c => c.jointEventId.Equals(jointExamRow.jointEventId) && c.jointGroupId.Equals(jointExamRow.jointGroupId)).FirstOrDefault();
  898. if (jointExamIdCollectorRowNow == null)
  899. {
  900. JointExamIdCollector jointExamIdCollectorRow = new JointExamIdCollector();
  901. jointExamIdCollectorRow.jointEventId = jointExamRow.jointEventId;
  902. jointExamIdCollectorRow.jointGroupId = jointExamRow.jointGroupId;
  903. jointExamIdCollectorsSchool.Add(jointExamIdCollectorRow);
  904. }
  905. }
  906. //jointExam列表
  907. jointExamInfo.Add(jointExamRow);
  908. }
  909. }
  910. if (iscontinuation)
  911. {
  912. token = item.ContinuationToken;
  913. break;
  914. }
  915. }
  916. //取得Exam
  917. List<ExamInfo> exams = new List<ExamInfo>();
  918. List<string> jointExamIds = jointExamInfo.Select(x => x.id).ToList();
  919. if (jointExamIds.Count > 0)
  920. {
  921. StringBuilder stringBuilderExam = new($"SELECT * FROM c WHERE CONTAINS(c.code, 'Exam-', true) AND ARRAY_CONTAINS({JsonSerializer.Serialize(jointExamIds)}, c.jointExamId)");
  922. await foreach (var item in client.GetContainer(Constant.TEAMModelOS, Constant.Common).GetItemQueryStreamIteratorSql(queryText: stringBuilderExam.ToString(), requestOptions: null))
  923. {
  924. using var json = await JsonDocument.ParseAsync(item.Content);
  925. if (json.RootElement.TryGetProperty("_count", out JsonElement count) && count.GetUInt16() > 0)
  926. {
  927. foreach (var obj in json.RootElement.GetProperty("Documents").EnumerateArray())
  928. {
  929. exams.Add(obj.ToObject<ExamInfo>());
  930. }
  931. }
  932. }
  933. }
  934. //取得老師報名的JointCourse
  935. List<JointEventGroupDb> jointEventGroup = new List<JointEventGroupDb>(); //個人課程 ※報名、決賽 一起取,最後整理時再分
  936. Dictionary<string, Dictionary<string,string>> courseToExamIdRegularDic = new Dictionary<string, Dictionary<string, string>>(); //活動評量ID => 課程ID、個人評量ID對應表 (報名)
  937. Dictionary<string, Dictionary<string, string>> courseToExamIdCustomDic = new Dictionary<string, Dictionary<string, string>>(); //活動評量ID => 課程ID、個人評量ID對應表 (決賽)
  938. if (jointExamIdCollectorsPrivate.Count > 0)
  939. {
  940. StringBuilder stringBuilderEventGroup = new($"SELECT * FROM c ");
  941. StringBuilder Where = new();
  942. foreach (JointExamIdCollector jointExamIdCollector in jointExamIdCollectorsPrivate)
  943. {
  944. if (Where.Length > 0)
  945. {
  946. Where.Append(" OR ");
  947. }
  948. Where.Append($" ( c.jointEventId = '{jointExamIdCollector.jointEventId}' AND c.jointGroupId = '{jointExamIdCollector.jointGroupId}' ) ");
  949. }
  950. stringBuilderEventGroup.Append(" WHERE ").Append(Where);
  951. await foreach (var item in client.GetContainer(Constant.TEAMModelOS, Constant.Teacher).GetItemQueryStreamIteratorSql(queryText: stringBuilderEventGroup.ToString(), requestOptions: new QueryRequestOptions() { PartitionKey = new PartitionKey("JointCourse") }))
  952. {
  953. using var json = await JsonDocument.ParseAsync(item.Content);
  954. if (json.RootElement.TryGetProperty("_count", out JsonElement count) && count.GetUInt16() > 0)
  955. {
  956. foreach (var obj in json.RootElement.GetProperty("Documents").EnumerateArray())
  957. {
  958. JointEventGroupDb jointEventGroupRow = obj.ToObject<JointEventGroupDb>();
  959. foreach(var course in jointEventGroupRow.courseLists)
  960. {
  961. if(jointEventGroupRow.scope.Equals("private") && string.IsNullOrWhiteSpace(course.subjectId) && !string.IsNullOrWhiteSpace(course.courseId)) course.subjectId = course.courseId;
  962. //課程ID => 活動ID、個人評量ID對應表製作
  963. List<ExamInfo> examByCreator = exams.Where(e => e.subjects.Select(s => s.id).ToList().Contains(course.courseId) && e.creatorId.Equals(jointEventGroupRow.creatorId)).ToList();
  964. if(!string.IsNullOrWhiteSpace(jointEventGroupRow.type) && jointEventGroupRow.type.Equals("custom")) //決賽
  965. {
  966. foreach (ExamInfo exam in examByCreator)
  967. {
  968. if(!courseToExamIdCustomDic.ContainsKey(exam.jointExamId))
  969. {
  970. courseToExamIdCustomDic.Add(exam.jointExamId, new Dictionary<string, string>());
  971. }
  972. if (!courseToExamIdCustomDic[exam.jointExamId].ContainsKey(course.courseId))
  973. {
  974. //courseToExamIdCustomDic[exam.jointExamId].Add(course.courseId, exam.id);
  975. if (exam.stuLists.Count.Equals(course.groupLists.Count)) //評量的班級數(stuLists)和報名的班級數相同者才放入 (一個統測評量有多個 個人評量對策)
  976. {
  977. courseToExamIdCustomDic[exam.jointExamId].Add(course.courseId, exam.id);
  978. }
  979. }
  980. }
  981. }
  982. else //報名
  983. {
  984. foreach (ExamInfo exam in examByCreator)
  985. {
  986. if (!courseToExamIdRegularDic.ContainsKey(exam.jointExamId))
  987. {
  988. courseToExamIdRegularDic.Add(exam.jointExamId, new Dictionary<string, string>());
  989. }
  990. if (!courseToExamIdRegularDic[exam.jointExamId].ContainsKey(course.courseId))
  991. {
  992. //courseToExamIdRegularDic[exam.jointExamId].Add(course.courseId, exam.id);
  993. if (exam.stuLists.Count.Equals(course.groupLists.Count))
  994. {
  995. courseToExamIdRegularDic[exam.jointExamId].Add(course.courseId, exam.id);
  996. }
  997. }
  998. }
  999. }
  1000. }
  1001. jointEventGroup.Add(jointEventGroupRow);
  1002. }
  1003. }
  1004. }
  1005. }
  1006. List<JointEventClassDb> jointEventClass = new List<JointEventClassDb>(); //學校班級 [待學校班級架構定義完成後再做]
  1007. if (jointExamIdCollectorsSchool.Count > 0)
  1008. {
  1009. }
  1010. //整理 jointExamInfo
  1011. if (jointExamInfo.Count > 0)
  1012. {
  1013. foreach(JointExam jointExam in jointExamInfo)
  1014. {
  1015. List<JointEventGroupDb> stuLists = new List<JointEventGroupDb>();
  1016. List<JointEventClassDb> classes = new List<JointEventClassDb>();
  1017. if (jointExam.examType.Equals("regular")) //一般競賽
  1018. {
  1019. stuLists = jointEventGroup.Where(g => g.jointEventId.Equals(jointExam.jointEventId) && g.jointGroupId.Equals(jointExam.jointGroupId) && (g.type == null || g.type.Equals("regular"))).ToList();
  1020. classes = jointEventClass.Where(g => g.jointEventId.Equals(jointExam.jointEventId) && g.jointGroupId.Equals(jointExam.jointGroupId) && (g.type == null || g.type.Equals("regular"))).ToList();
  1021. }
  1022. else if(jointExam.examType.Equals("custom")) //決賽
  1023. {
  1024. stuLists = jointEventGroup.Where(g => g.jointEventId.Equals(jointExam.jointEventId) && g.jointGroupId.Equals(jointExam.jointGroupId) && (g.type.Equals("custom"))).ToList();
  1025. classes = jointEventClass.Where(g => g.jointEventId.Equals(jointExam.jointEventId) && g.jointGroupId.Equals(jointExam.jointGroupId) && (g.type.Equals("custom"))).ToList();
  1026. }
  1027. //private (stuLists)
  1028. if (stuLists.Count > 0)
  1029. {
  1030. foreach (JointEventGroupDb stu in stuLists)
  1031. {
  1032. JointEventGroupBase stuListRow = new JointEventGroupBase();
  1033. stuListRow.scope = "private";
  1034. stuListRow.type = stu.type;
  1035. stuListRow.creatorId = stu.creatorId;
  1036. stuListRow.creatorName = stu.creatorName;
  1037. stuListRow.creatorEmail = stu.creatorEmail;
  1038. stuListRow.schoolId = stu.schoolId;
  1039. stuListRow.schoolName = stu.schoolName;
  1040. stuListRow.courseLists = Newtonsoft.Json.JsonConvert.DeserializeObject<List<JointEventGroupBase.JointEventGroupCourse>>(Newtonsoft.Json.JsonConvert.SerializeObject(stu.courseLists));
  1041. Dictionary<string, Dictionary<string, string>> courseToExamIdDic = (!string.IsNullOrWhiteSpace(stu.type) && stu.type.Equals("custom")) ? courseToExamIdCustomDic : courseToExamIdRegularDic;
  1042. foreach (JointEventGroupBase.JointEventGroupCourse course in stuListRow.courseLists)
  1043. {
  1044. course.examId = (courseToExamIdDic.ContainsKey(jointExam.id) && courseToExamIdDic[jointExam.id].ContainsKey(course.courseId)) ? courseToExamIdDic[jointExam.id][course.courseId] : null;
  1045. }
  1046. jointExam.stuLists.Add(stuListRow);
  1047. }
  1048. }
  1049. //school (classes)
  1050. if (classes.Count > 0)
  1051. {
  1052. foreach (JointEventClassDb clas in classes)
  1053. {
  1054. JointEventClassBase classRow = new JointEventClassBase();
  1055. //樣式未定
  1056. jointExam.classes.Add(classRow);
  1057. }
  1058. }
  1059. }
  1060. }
  1061. return Ok(new { data = jointExamInfo, token = token });
  1062. }
  1063. catch (Exception e)
  1064. {
  1065. //await _dingDing.SendBotMsg($"OS,{_option.Location},jointevent/jointexam/find()\n{e.Message}\n{e.StackTrace}", GroupNames.醍摩豆服務運維群組);
  1066. return BadRequest();
  1067. }
  1068. }
  1069. /// <summary>
  1070. /// 新建變更統測評量
  1071. /// </summary>
  1072. /// <param name="request"></param>
  1073. /// <returns></returns>
  1074. [ProducesDefaultResponseType]
  1075. [Authorize(Roles = "IES")]
  1076. #if !DEBUG
  1077. [AuthToken(Roles = "teacher")]
  1078. #endif
  1079. [HttpPost("exam/upsert")]
  1080. public async Task<IActionResult> upsertJointExam(JsonElement request)
  1081. {
  1082. try
  1083. {
  1084. var client = _azureCosmos.GetCosmosClient();
  1085. long now = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds();
  1086. string jointEventId = (request.TryGetProperty("jointEventId", out JsonElement _jointEventId)) ? _jointEventId.ToString() : string.Empty;
  1087. string jointGroupId = (request.TryGetProperty("jointGroupId", out JsonElement _jointGroupId)) ? _jointGroupId.ToString() : string.Empty;
  1088. string jointScheduleId = (request.TryGetProperty("jointScheduleId", out JsonElement _jointScheduleId)) ? _jointScheduleId.ToString() : string.Empty;
  1089. string jointExamId = (request.TryGetProperty("jointExamId", out JsonElement _jointExamId)) ? _jointExamId.ToString() : string.Empty;
  1090. string creatorId = (request.TryGetProperty("creatorId", out JsonElement _creatorId)) ? _creatorId.ToString() : string.Empty;
  1091. string name = (request.TryGetProperty("name", out JsonElement _name)) ? _name.ToString() : string.Empty;
  1092. string source = (request.TryGetProperty("source", out JsonElement _source)) ? _source.ToString() : string.Empty;
  1093. string scope = (request.TryGetProperty("scope", out JsonElement _scope)) ? _scope.ToString() : "private";
  1094. List<PaperSimple> papers = (request.TryGetProperty("papers", out JsonElement _papers)) ? _papers.ToObject<List<PaperSimple>>() : null;
  1095. List<JointEventGroupBase> stuLists = (request.TryGetProperty("stuLists", out JsonElement _stuLists)) ? _stuLists.ToObject<List<JointEventGroupBase>>() : null;
  1096. List<JointEventClassBase> classes = (request.TryGetProperty("classes", out JsonElement _classes)) ? _classes.ToObject<List<JointEventClassBase>>() : null;
  1097. if (string.IsNullOrWhiteSpace(jointEventId) || string.IsNullOrWhiteSpace(jointGroupId) || string.IsNullOrWhiteSpace(jointScheduleId) || string.IsNullOrWhiteSpace(jointExamId))
  1098. {
  1099. return BadRequest();
  1100. }
  1101. //取得schedule
  1102. JointEventGroup jointEventGroup = new JointEventGroup();
  1103. JointEventSchedule jointEventSchedule = new JointEventSchedule();
  1104. StringBuilder stringBuilderSchedule = new($"SELECT cg.id as groupId, cg.name as groupName, cg.assistants, cs.id as scheduleId, cs.type as scheduleType, cs.examType, cs.examOverwrite, cs.name as scheduleName, cs.startTime as scheduleStartTime, cs.endTime as scheduleEndTime, cs.progress as scheduleProgress FROM c JOIN cs IN c.schedule JOIN cg IN c.groups WHERE c.id = '{jointEventId}' AND cs.id = '{jointScheduleId}' AND cg.id = '{jointGroupId}' ");
  1105. await foreach (var item in client.GetContainer(Constant.TEAMModelOS, "Teacher").GetItemQueryStreamIteratorSql(queryText: stringBuilderSchedule.ToString(), requestOptions: new QueryRequestOptions() { PartitionKey = new PartitionKey("JointEvent") }))
  1106. {
  1107. using var json = await JsonDocument.ParseAsync(item.Content);
  1108. if (json.RootElement.TryGetProperty("_count", out JsonElement count) && count.GetUInt16() > 0)
  1109. {
  1110. foreach (var obj in json.RootElement.GetProperty("Documents").EnumerateArray())
  1111. {
  1112. jointEventGroup.id = obj.GetProperty("groupId").GetString();
  1113. jointEventGroup.name = obj.GetProperty("groupName").GetString();
  1114. jointEventGroup.assistants = obj.GetProperty("assistants").ToObject<HashSet<string>>();
  1115. jointEventSchedule.id = obj.GetProperty("scheduleId").GetString();
  1116. jointEventSchedule.examType = obj.GetProperty("examType").GetString();
  1117. jointEventSchedule.examOverwrite = obj.GetProperty("examOverwrite").GetBoolean();
  1118. jointEventSchedule.type = obj.GetProperty("scheduleType").GetString();
  1119. jointEventSchedule.name = obj.GetProperty("scheduleName").GetString();
  1120. jointEventSchedule.startTime = obj.GetProperty("scheduleStartTime").GetInt64();
  1121. jointEventSchedule.endTime = obj.GetProperty("scheduleEndTime").GetInt64();
  1122. jointEventSchedule.progress = obj.GetProperty("scheduleProgress").GetString();
  1123. }
  1124. }
  1125. }
  1126. if (string.IsNullOrWhiteSpace(jointEventGroup.id) || string.IsNullOrWhiteSpace(jointEventSchedule.id))
  1127. {
  1128. return Ok(new { errCode = "3", err = "Invalid jointEventId." });
  1129. }
  1130. JointExam jointExam = new JointExam();
  1131. string mode = "upd";
  1132. try
  1133. {
  1134. jointExam = await client.GetContainer(Constant.TEAMModelOS, "Common").ReadItemAsync<JointExam>(jointExamId, new PartitionKey("JointExam"));
  1135. }
  1136. catch (Exception e)
  1137. {
  1138. mode = "add";
  1139. }
  1140. if (mode.Equals("upd")) //更新
  1141. {
  1142. if (jointExam.progress.Equals("going"))
  1143. {
  1144. return Ok(new { errCode = "9", err = "JointExam is going, can't be changed." });
  1145. }
  1146. if (!string.IsNullOrWhiteSpace(name)) jointExam.name = name;
  1147. if (!string.IsNullOrWhiteSpace(source)) jointExam.source = source;
  1148. //if (jointEventSchedule.examType.Equals("custom")) //決賽,要填classes或stuLists擇一必須
  1149. //{
  1150. // if (classes == null || stuLists == null)
  1151. // {
  1152. // return Ok(new { errCode = "8", err = "Invalid classes or stuLists." });
  1153. // }
  1154. // if (classes != null) jointExam.classes = classes;
  1155. // if (stuLists != null) jointExam.stuLists = stuLists;
  1156. //}
  1157. if (papers != null) jointExam.papers = papers;
  1158. }
  1159. else //新建
  1160. {
  1161. if (papers == null) return Ok(new { errCode = "7", err = "Invalid paper." });
  1162. if (string.IsNullOrWhiteSpace(creatorId)) return Ok(new { errCode = "8", err = "Invalid creatorId." });
  1163. if (string.IsNullOrWhiteSpace(name)) return Ok(new { errCode = "9", err = "Invalid name." });
  1164. if (string.IsNullOrWhiteSpace(source)) return Ok(new { errCode = "10", err = "Invalid source." });
  1165. //if (jointEventSchedule.examType.Equals("custom")) //決賽,要填classes或stuLists擇一必須
  1166. //{
  1167. // if (classes == null || stuLists == null)
  1168. // {
  1169. // return Ok(new { errCode = "8", err = "Invalid classes or stuLists." });
  1170. // }
  1171. // if (classes != null) jointExam.classes = classes;
  1172. // if (stuLists != null) jointExam.stuLists = stuLists;
  1173. //}
  1174. jointExam.pk = "JointExam";
  1175. jointExam.code = "JointExam";
  1176. jointExam.id = jointExamId;
  1177. jointExam.jointEventId = jointEventId;
  1178. jointExam.jointScheduleId = jointScheduleId;
  1179. jointExam.jointGroupId = jointGroupId;
  1180. jointExam.creatorId = creatorId;
  1181. jointExam.name = name;
  1182. jointExam.source = source;
  1183. jointExam.papers = papers;
  1184. jointExam.createTime = now;
  1185. jointExam.scope = scope;
  1186. }
  1187. //共通
  1188. jointExam.examType = jointEventSchedule.examType;
  1189. jointExam.examOverwrite = jointEventSchedule.examOverwrite;
  1190. jointExam.startTime = jointEventSchedule.startTime;
  1191. jointExam.endTime = jointEventSchedule.endTime;
  1192. jointExam.progress = (jointExam.startTime > now) ? "pending" : (now > jointExam.endTime) ? "finish" : "going";
  1193. jointExam.updateTime = now;
  1194. await client.GetContainer(Constant.TEAMModelOS, "Common").UpsertItemAsync<JointExam>(jointExam, new PartitionKey("JointExam"));
  1195. return Ok(new { errCode = "", err = "", jointExam });
  1196. }
  1197. catch (Exception e)
  1198. {
  1199. //await _dingDing.SendBotMsg($"OS,{_option.Location},jointevent/jointexam/find()\n{e.Message}\n{e.StackTrace}", GroupNames.醍摩豆服務運維群組);
  1200. return BadRequest();
  1201. }
  1202. }
  1203. /// <summary>
  1204. /// 取得TMID的Blob access token
  1205. /// </summary>
  1206. /// <param name="request"></param>
  1207. /// <returns></returns>
  1208. [ProducesDefaultResponseType]
  1209. [Authorize(Roles = "IES")]
  1210. #if !DEBUG
  1211. [AuthToken(Roles = "teacher")]
  1212. #endif
  1213. [HttpPost("tblobsas")]
  1214. public async Task<IActionResult> getTeacherBlobSas(JsonElement request)
  1215. {
  1216. List<string> tmids = (request.TryGetProperty("tmids", out JsonElement _tmids)) ? _tmids.ToObject<List<string>>() : new List<string> ();
  1217. var client = _azureCosmos.GetCosmosClient();
  1218. //取得老師ID
  1219. List<string> tmidsExist = new List<string>();
  1220. if (tmids.Count > 0)
  1221. {
  1222. StringBuilder stringBuilder = new($"SELECT c.id FROM c WHERE ARRAY_CONTAINS({JsonSerializer.Serialize(tmids)}, c.id) ");
  1223. await foreach (var item in client.GetContainer(Constant.TEAMModelOS, "Teacher").GetItemQueryStreamIteratorSql(queryText: stringBuilder.ToString(), requestOptions: new QueryRequestOptions() { PartitionKey = new PartitionKey("Base") }))
  1224. {
  1225. using var json = await JsonDocument.ParseAsync(item.Content);
  1226. if (json.RootElement.TryGetProperty("_count", out JsonElement count) && count.GetUInt16() > 0)
  1227. {
  1228. foreach (var obj in json.RootElement.GetProperty("Documents").EnumerateArray())
  1229. {
  1230. tmidsExist.Add(obj.GetProperty("id").ToString());
  1231. }
  1232. }
  1233. }
  1234. }
  1235. //取得SAS
  1236. List<object> sasList = new List<object>();
  1237. if (tmidsExist.Count > 0)
  1238. {
  1239. foreach (string tmid in tmidsExist)
  1240. {
  1241. var (blob_uri, blob_sas) = _azureStorage.GetBlobContainerSAS(tmid, BlobContainerSasPermissions.Read | BlobContainerSasPermissions.List);
  1242. sasList.Add(new { id = tmid, sas = blob_sas });
  1243. }
  1244. }
  1245. return Ok(sasList);
  1246. }
  1247. //判斷某groupId在某JointSchedule是否完成
  1248. private async Task<string> GetGroupFinishJointSchedule(string jointEventId, string jointGroupId, string groupId, JointEventSchedule schedule)
  1249. {
  1250. string result = "undo";
  1251. var client = _azureCosmos.GetCosmosClient();
  1252. string scope = "private"; //先只指定個人
  1253. string container = (scope.Equals("school")) ? Constant.School : Constant.Teacher;
  1254. switch (schedule.type)
  1255. {
  1256. //報名
  1257. case "join":
  1258. StringBuilder stringBuilder = new($"SELECT DISTINCT ccg.id FROM c JOIN cc IN c.courseLists JOIN ccg IN cc.groupLists WHERE c.jointEventId = '{jointEventId}' AND c.jointGroupId = '{jointGroupId}' AND (c.type = 'regular' OR NOT IS_DEFINED(c.type) OR IS_NULL(c.type)) AND ccg.id = '{groupId}' ");
  1259. await foreach (var item in client.GetContainer(Constant.TEAMModelOS, container).GetItemQueryStreamIteratorSql(queryText: stringBuilder.ToString(), requestOptions: new QueryRequestOptions() { PartitionKey = new PartitionKey("JointCourse") }))
  1260. {
  1261. using var json = await JsonDocument.ParseAsync(item.Content);
  1262. if (json.RootElement.TryGetProperty("_count", out JsonElement count) && count.GetUInt16() > 0)
  1263. {
  1264. foreach (var obj in json.RootElement.GetProperty("Documents").EnumerateArray())
  1265. {
  1266. if (obj.GetProperty("id").GetString().Equals(groupId))
  1267. {
  1268. result = "complete";
  1269. }
  1270. }
  1271. }
  1272. }
  1273. break;
  1274. //競賽
  1275. case "exam":
  1276. //取得老師報名課程或決賽老師課程
  1277. List<JointEventGroupDb> jointEventGroup = new List<JointEventGroupDb>(); //個人課程
  1278. StringBuilder stringBuilderEventGroup = new($"SELECT * FROM c WHERE c.jointEventId = '{jointEventId}' AND c.jointGroupId = '{jointGroupId}' ");
  1279. if (schedule.examType.Equals("regular")) //熱身賽
  1280. {
  1281. stringBuilderEventGroup.Append($" AND (c.type = 'regular' OR NOT IS_DEFINED(c.type) OR IS_NULL(c.type)) ");
  1282. }
  1283. else if(schedule.examType.Equals("custom")) //決賽
  1284. {
  1285. stringBuilderEventGroup.Append($" AND c.type = 'custom' AND c.jointScheduleId = '{schedule.id}' ");
  1286. }
  1287. await foreach (var item in client.GetContainer(Constant.TEAMModelOS, Constant.Teacher).GetItemQueryStreamIteratorSql(queryText: stringBuilderEventGroup.ToString(), requestOptions: new QueryRequestOptions() { PartitionKey = new PartitionKey("JointCourse") }))
  1288. {
  1289. using var json = await JsonDocument.ParseAsync(item.Content);
  1290. if (json.RootElement.TryGetProperty("_count", out JsonElement count) && count.GetUInt16() > 0)
  1291. {
  1292. foreach (var obj in json.RootElement.GetProperty("Documents").EnumerateArray())
  1293. {
  1294. jointEventGroup.Add(obj.ToObject<JointEventGroupDb>());
  1295. }
  1296. }
  1297. }
  1298. //取得本Schedule的所有JointExam
  1299. List<string> jointExamIdList = new List<string>();
  1300. StringBuilder stringBuilderJointExam = new($"SELECT DISTINCT VALUE c.id FROM c WHERE c.jointEventId = '{jointEventId}' AND c.jointGroupId = '{jointGroupId}' AND c.jointScheduleId = '{schedule.id}' ");
  1301. var resultJointExam = await _azureCosmos.GetCosmosClient().GetContainer(Constant.TEAMModelOS, Constant.Common).GetList<string>(stringBuilderJointExam.ToString(), $"JointExam");
  1302. if (resultJointExam.list.IsNotEmpty())
  1303. {
  1304. jointExamIdList = new List<string>(resultJointExam.list);
  1305. }
  1306. //取得所有個人評量
  1307. List<string> examIdList = new List<string>();
  1308. string sqlExam = $"SELECT DISTINCT VALUE c.id FROM c WHERE ARRAY_CONTAINS({JsonSerializer.Serialize(jointExamIdList)}, c.jointExamId) AND ARRAY_CONTAINS(c.stuLists, '{groupId}') AND CONTAINS(c.code, 'Exam-')";
  1309. await foreach (var item in client.GetContainer(Constant.TEAMModelOS, Constant.Common).GetItemQueryIteratorSql<string>(queryText: sqlExam, requestOptions: new QueryRequestOptions { }))
  1310. {
  1311. examIdList.Add(item);
  1312. }
  1313. //取得所有考試的作答結果
  1314. List<string> finishExamIdList = new List<string>();
  1315. string sqlExamClassResult = $"SELECT c.examId, c.info.id as classId, c.studentAnswers FROM c WHERE ARRAY_CONTAINS({JsonSerializer.Serialize(examIdList)}, c.examId) AND c.info.id = '{groupId}' AND c.progress=true AND CONTAINS(c.code, 'ExamClassResult')";
  1316. await foreach (var item in client.GetContainer(Constant.TEAMModelOS, Constant.Common).GetItemQueryStreamIteratorSql(queryText: sqlExamClassResult, requestOptions: new QueryRequestOptions() { }))
  1317. {
  1318. using var json = await JsonDocument.ParseAsync(item.Content);
  1319. if (json.RootElement.TryGetProperty("_count", out JsonElement count) && count.GetUInt16() > 0)
  1320. {
  1321. foreach (var obj in json.RootElement.GetProperty("Documents").EnumerateArray())
  1322. {
  1323. string examId = obj.GetProperty("examId").ToString();
  1324. string classId = obj.GetProperty("classId").ToString();
  1325. List<List<string>> studentAnswers = obj.GetProperty("studentAnswers").ToObject<List<List<string>>>();
  1326. bool isFinish = false; //評量是否已完成 ※有任一學生有作答則視為已完成
  1327. foreach (List<string> studentAnswer in studentAnswers)
  1328. {
  1329. if (studentAnswer.Count > 0) { isFinish = true; break; }
  1330. }
  1331. if (isFinish)
  1332. {
  1333. finishExamIdList.Add(examId);
  1334. }
  1335. }
  1336. }
  1337. }
  1338. //結果判斷
  1339. if(jointEventGroup.Count.Equals(0)) //資格不符
  1340. {
  1341. result = "disqualify";
  1342. }
  1343. else if (jointExamIdList.Count > 0 && jointExamIdList.Count.Equals(finishExamIdList.Count))
  1344. {
  1345. result = "complete";
  1346. }
  1347. else if (jointExamIdList.Count > 0 && finishExamIdList.Count > 0 && jointExamIdList.Count > finishExamIdList.Count)
  1348. {
  1349. result = "doing";
  1350. }
  1351. break;
  1352. }
  1353. return result;
  1354. }
  1355. //生成決賽名單
  1356. [ProducesDefaultResponseType]
  1357. #if !DEBUG
  1358. [Authorize(Roles = "IES")]
  1359. [AuthToken(Roles = "teacher")]
  1360. #endif
  1361. [HttpPost("exam/create-final-teachercourse")]
  1362. public async Task<IActionResult> createFinalJointTeacherCourse(JsonElement request)
  1363. {
  1364. List<JointEventGroupDb> result = new List<JointEventGroupDb>();
  1365. var client = _azureCosmos.GetCosmosClient();
  1366. string jointEventId = (request.TryGetProperty("jointEventId", out JsonElement _jointEventId)) ? _jointEventId.ToString() : string.Empty;
  1367. string jointScheduleId = (request.TryGetProperty("jointScheduleId", out JsonElement _jointScheduleId)) ? _jointScheduleId.ToString() : string.Empty; //要計算的熱身賽行程
  1368. string scope = "private";
  1369. JointEvent jointEvent = await client.GetContainer(Constant.TEAMModelOS, "Teacher").ReadItemAsync<JointEvent>(jointEventId, new PartitionKey("JointEvent"));
  1370. if (jointEvent == null)
  1371. {
  1372. return BadRequest();
  1373. }
  1374. List<string> jointGroupIds = jointEvent.groups.Select(g => g.id).ToList();
  1375. if(jointGroupIds.Count > 0)
  1376. {
  1377. foreach(string jointGroupId in jointGroupIds)
  1378. {
  1379. List<JointEventGroupDb> addResult = await JointService.CreatePassJointCourseBySchedule(client, jointEventId, jointGroupId, jointScheduleId, scope);
  1380. result = result.Union(addResult).ToList();
  1381. }
  1382. }
  1383. return Ok(result);
  1384. }
  1385. /// <summary>
  1386. /// 變更個人統測評量的可見Flag
  1387. /// </summary>
  1388. /// <param name="request"></param>
  1389. /// <returns></returns>
  1390. [ProducesDefaultResponseType]
  1391. [Authorize(Roles = "IES")]
  1392. #if !DEBUG
  1393. [AuthToken(Roles = "teacher")]
  1394. #endif
  1395. [HttpPost("exam/set-visible")]
  1396. public async Task<IActionResult> setJointExamVisible(JsonElement request)
  1397. {
  1398. var client = _azureCosmos.GetCosmosClient();
  1399. long now = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds();
  1400. string jointEventId = (request.TryGetProperty("jointEventId", out JsonElement _jointEventId)) ? _jointEventId.ToString() : string.Empty;
  1401. string jointScheduleId = (request.TryGetProperty("jointScheduleId", out JsonElement _jointScheduleId)) ? _jointScheduleId.ToString() : string.Empty;
  1402. bool visiable = (request.TryGetProperty("visiable", out JsonElement _visiable)) ? _visiable.GetBoolean() : true;
  1403. if (string.IsNullOrWhiteSpace(jointEventId) || string.IsNullOrWhiteSpace(jointScheduleId))
  1404. {
  1405. return BadRequest();
  1406. }
  1407. //取得活動行程
  1408. JointEventSchedule jointEventSchedule = new JointEventSchedule();
  1409. StringBuilder stringBuilderSchedule = new($"SELECT cs.id, cs.type, cs.examType, cs.examOverwrite, cs.name, cs.startTime, cs.endTime, cs.progress FROM c JOIN cs IN c.schedule WHERE c.id = '{jointEventId}' AND cs.id = '{jointScheduleId}' ");
  1410. await foreach (var item in client.GetContainer(Constant.TEAMModelOS, Constant.Teacher).GetItemQueryStreamIteratorSql(queryText: stringBuilderSchedule.ToString(), requestOptions: new QueryRequestOptions() { PartitionKey = new PartitionKey("JointEvent") }))
  1411. {
  1412. using var json = await JsonDocument.ParseAsync(item.Content);
  1413. if (json.RootElement.TryGetProperty("_count", out JsonElement count) && count.GetUInt16() > 0)
  1414. {
  1415. foreach (var obj in json.RootElement.GetProperty("Documents").EnumerateArray())
  1416. {
  1417. jointEventSchedule.id = obj.GetProperty("id").GetString();
  1418. jointEventSchedule.examType = obj.GetProperty("examType").GetString();
  1419. jointEventSchedule.examOverwrite = obj.GetProperty("examOverwrite").GetBoolean();
  1420. jointEventSchedule.type = obj.GetProperty("type").GetString();
  1421. jointEventSchedule.name = obj.GetProperty("name").GetString();
  1422. jointEventSchedule.startTime = obj.GetProperty("startTime").GetInt64();
  1423. jointEventSchedule.endTime = obj.GetProperty("endTime").GetInt64();
  1424. jointEventSchedule.progress = obj.GetProperty("progress").GetString();
  1425. }
  1426. }
  1427. }
  1428. if (string.IsNullOrWhiteSpace(jointEventSchedule.id))
  1429. {
  1430. return Ok(new { errCode = "5", err = "Invalid eventId or scheduleId." });
  1431. }
  1432. //取得活動評量ID
  1433. List<string> jointExamIds = new List<string>();
  1434. if(jointEventSchedule.type.Equals("exam") && jointEventSchedule.examType.Equals("custom"))
  1435. {
  1436. StringBuilder stringBuilderJointExam = new($"SELECT DISTINCT VALUE c.id FROM c WHERE c.jointEventId = '{jointEventId}' AND c.jointScheduleId = '{jointScheduleId}' ");
  1437. await foreach (var item in client.GetContainer(Constant.TEAMModelOS, Constant.Common).GetItemQueryIteratorSql<string>(queryText: stringBuilderJointExam.ToString(), requestOptions: new QueryRequestOptions() { PartitionKey = new PartitionKey("JointExam") }))
  1438. {
  1439. jointExamIds.Add(item);
  1440. }
  1441. }
  1442. //取得個人評量
  1443. List<string> updExamIds = new List<string>();
  1444. if (jointExamIds.Any())
  1445. {
  1446. List<ExamInfo> exams = new List<ExamInfo>();
  1447. StringBuilder stringBuilderExam = new($"SELECT * FROM c WHERE c.pk = 'Exam' AND ARRAY_CONTAINS({JsonSerializer.Serialize(jointExamIds)}, c.jointExamId) ");
  1448. await foreach (var item in client.GetContainer(Constant.TEAMModelOS, Constant.Common).GetItemQueryStreamIteratorSql(queryText: stringBuilderExam.ToString(), requestOptions: null ))
  1449. {
  1450. using var json = await JsonDocument.ParseAsync(item.Content);
  1451. if (json.RootElement.TryGetProperty("_count", out JsonElement count) && count.GetUInt16() > 0)
  1452. {
  1453. foreach (var obj in json.RootElement.GetProperty("Documents").EnumerateArray())
  1454. {
  1455. exams.Add(obj.ToObject<ExamInfo>());
  1456. }
  1457. }
  1458. }
  1459. if(exams.Any())
  1460. {
  1461. foreach(ExamInfo exam in exams)
  1462. {
  1463. if(!string.IsNullOrWhiteSpace(exam.jointExamId))
  1464. {
  1465. exam.jointVisiable = visiable;
  1466. await client.GetContainer(Constant.TEAMModelOS, Constant.Common).ReplaceItemAsync(exam, exam.id);
  1467. updExamIds.Add(exam.id);
  1468. }
  1469. }
  1470. }
  1471. }
  1472. return Ok(new { errCode = "", err = "", updExamIds });
  1473. }
  1474. /// <summary>
  1475. /// 手動建立個人統測評量
  1476. /// </summary>
  1477. /// <param name="request"></param>
  1478. /// <returns></returns>
  1479. [ProducesDefaultResponseType]
  1480. [Authorize(Roles = "IES")]
  1481. #if !DEBUG
  1482. [AuthToken(Roles = "teacher")]
  1483. #endif
  1484. [HttpPost("exam/create-exam-by-joint")]
  1485. public async Task<IActionResult> createExamByJoint(JsonElement request)
  1486. {
  1487. var client = _azureCosmos.GetCosmosClient();
  1488. long now = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds();
  1489. string jointEventId = (request.TryGetProperty("jointEventId", out JsonElement _jointEventId)) ? _jointEventId.ToString() : string.Empty;
  1490. string jointScheduleId = (request.TryGetProperty("jointScheduleId", out JsonElement _jointScheduleId)) ? _jointScheduleId.ToString() : string.Empty;
  1491. string jointExamId = (request.TryGetProperty("jointExamId", out JsonElement _jointExamId)) ? _jointExamId.ToString() : string.Empty;
  1492. string tmid = (request.TryGetProperty("tmid", out JsonElement _tmid)) ? _tmid.ToString() : string.Empty;
  1493. if (string.IsNullOrWhiteSpace(jointEventId) || string.IsNullOrWhiteSpace(jointScheduleId))
  1494. {
  1495. return BadRequest();
  1496. }
  1497. //取得活動行程
  1498. JointEventSchedule jointEventSchedule = new JointEventSchedule();
  1499. StringBuilder stringBuilderSchedule = new($"SELECT cs.id, cs.type, cs.examType, cs.examOverwrite, cs.name, cs.startTime, cs.endTime, cs.progress FROM c JOIN cs IN c.schedule WHERE c.id = '{jointEventId}' AND cs.id = '{jointScheduleId}' ");
  1500. await foreach (var item in client.GetContainer(Constant.TEAMModelOS, Constant.Teacher).GetItemQueryStreamIteratorSql(queryText: stringBuilderSchedule.ToString(), requestOptions: new QueryRequestOptions() { PartitionKey = new PartitionKey("JointEvent") }))
  1501. {
  1502. using var json = await JsonDocument.ParseAsync(item.Content);
  1503. if (json.RootElement.TryGetProperty("_count", out JsonElement count) && count.GetUInt16() > 0)
  1504. {
  1505. foreach (var obj in json.RootElement.GetProperty("Documents").EnumerateArray())
  1506. {
  1507. jointEventSchedule.id = obj.GetProperty("id").GetString();
  1508. jointEventSchedule.examType = obj.GetProperty("examType").GetString();
  1509. jointEventSchedule.examOverwrite = obj.GetProperty("examOverwrite").GetBoolean();
  1510. jointEventSchedule.type = obj.GetProperty("type").GetString();
  1511. jointEventSchedule.name = obj.GetProperty("name").GetString();
  1512. jointEventSchedule.startTime = obj.GetProperty("startTime").GetInt64();
  1513. jointEventSchedule.endTime = obj.GetProperty("endTime").GetInt64();
  1514. jointEventSchedule.progress = obj.GetProperty("progress").GetString();
  1515. }
  1516. }
  1517. }
  1518. if (string.IsNullOrWhiteSpace(jointEventSchedule.id))
  1519. {
  1520. return Ok(new { errCode = "5", err = "Invalid eventId or scheduleId." });
  1521. }
  1522. //取得活動評量ID
  1523. List<JointExam> jointExams = new List<JointExam>();
  1524. StringBuilder stringBuilderJointExam = new($"SELECT * FROM c WHERE c.jointEventId = '{jointEventId}' AND c.jointScheduleId = '{jointScheduleId}' ");
  1525. if (!string.IsNullOrWhiteSpace(jointExamId)) stringBuilderJointExam.Append($" AND c.id = '{jointExamId}'");
  1526. await foreach (var item in client.GetContainer(Constant.TEAMModelOS, Constant.Common).GetItemQueryStreamIteratorSql(queryText: stringBuilderJointExam.ToString(), requestOptions: new QueryRequestOptions() { PartitionKey = new PartitionKey("JointExam") }))
  1527. {
  1528. using var json = await JsonDocument.ParseAsync(item.Content);
  1529. if (json.RootElement.TryGetProperty("_count", out JsonElement count) && count.GetUInt16() > 0)
  1530. {
  1531. foreach (var obj in json.RootElement.GetProperty("Documents").EnumerateArray())
  1532. {
  1533. jointExams.Add(obj.ToObject<JointExam>());
  1534. }
  1535. }
  1536. }
  1537. //Exam生成邏輯
  1538. if (jointExams.Count > 0)
  1539. {
  1540. foreach (var info in jointExams)
  1541. {
  1542. //生成統測活動所有的個人評量
  1543. await JointService.GenerateExamFromJointExamAsync(client, _azureStorage, _serviceBus, _coreAPIHttpService, _azureRedis, _configuration, _dingDing, info, tmid);
  1544. }
  1545. }
  1546. return Ok(new { errCode = "", err = "" });
  1547. }
  1548. public class JointExamIdCollector
  1549. {
  1550. public string jointEventId { get; set; }
  1551. public string jointGroupId { get; set; }
  1552. }
  1553. /// <summary>
  1554. /// 統測活動 API輸出用
  1555. /// </summary>
  1556. public class JointEventDto : JointEvent
  1557. {
  1558. public string blobsas { get; set; }
  1559. }
  1560. /// <summary>
  1561. /// 統測活動老師報名課程 API輸出用
  1562. /// </summary>
  1563. public class JointEventGroupDto
  1564. {
  1565. public string id { get; set; }
  1566. public string code { get; set; }
  1567. public string pk { get; set; }
  1568. public string jointEventId { get; set; }
  1569. public string jointGroupId { get; set; }
  1570. public string scope { get; set; } //school:學校(選課班) private:個人
  1571. public string type { get; set; } //regular:報名資訊 custom:決賽名單
  1572. public string creatorId { get; set; }
  1573. public string creatorName { get; set; }
  1574. public string schoolId { get; set; } //老師教育雲綁定的學校ID
  1575. public string schoolName { get; set; } //老師教育雲綁定的學校名稱
  1576. public string periodId { get; set; } //老師個人課程時為null
  1577. public string countryId { get; set; }
  1578. public string countryName { get; set; }
  1579. public string provinceId { get; set; }
  1580. public string provinceName { get; set; }
  1581. public string cityId { get; set; }
  1582. public string cityName { get; set; }
  1583. public List<JointEventGroupCourseDto> courseLists { get; set; } = new();
  1584. public long createTime { get; set; }
  1585. public class JointEventGroupCourseDto
  1586. {
  1587. public string subjectId { get; set; } //老師個人課程時為null
  1588. public string courseId { get; set; }
  1589. public string courseName { get; set; }
  1590. public List<JointEventGroupCourseGroupDto> groupLists { get; set; } = new();
  1591. }
  1592. public class JointEventGroupCourseGroupDto
  1593. {
  1594. public string id { get; set; }
  1595. public string name { get; set; }
  1596. public int stuNum { get; set; }
  1597. public List<object> schedule { get; set; } = new List<object>(); //已完成的行程
  1598. }
  1599. }
  1600. }
  1601. }