Przeglądaj źródła

統測活動 報名班級追加各活動階段進行狀況

jeff 4 miesięcy temu
rodzic
commit
3b036b2472

+ 51 - 0
TEAMModelOS.Function/CosmosDBTriggers/TriggerExam.cs

@@ -34,6 +34,7 @@ using System.ComponentModel;
 using TEAMModelOS.Function;
 using DocumentFormat.OpenXml.Spreadsheet;
 using Items = TEAMModelOS.SDK.Models.Cosmos.Student.Items;
+using static TEAMModelOS.SDK.Models.JointEvent;
 namespace TEAMModelOS.CosmosDBTriggers
 {
     public class TriggerExam
@@ -393,6 +394,31 @@ namespace TEAMModelOS.CosmosDBTriggers
                                         await client.GetContainer(Constant.TEAMModelOS, "Common").ReplaceItemAsync<ExamInfo>(info, info.id, new PartitionKey(info.code));
                                     }
                                 }*/
+
+                                //統測活動特別處理
+                                ///活動行進狀態更新
+                                if (!string.IsNullOrWhiteSpace(info.jointExamId))
+                                {
+                                    try
+                                    {
+                                        //取得統測評量活動
+                                        JointExam jointExam = await client.GetContainer(Constant.TEAMModelOS, "Common").ReadItemAsync<JointExam>(info.jointExamId, new PartitionKey("JointExam"));
+                                        string jointEventId = jointExam.jointEventId;
+                                        string jointScheduleId = jointExam.jointScheduleId;
+                                        string jointGroupId = jointExam.jointGroupId;
+                                        string creatorId = info.creatorId;
+                                        JointEvent jointEvent = await client.GetContainer(Constant.TEAMModelOS, "Teacher").ReadItemAsync<JointEvent>(jointEventId, new PartitionKey("JointEvent"));
+                                        JointEventSchedule schedule = jointEvent.schedule.First(s => s.id.Equals(jointScheduleId));
+                                        JointEventGroupDb jointEventGroupDb = await JointService.CalJointCourseGroupScheduleStatusAsync(client, jointEventId, jointGroupId, creatorId, null, schedule);
+                                        if(jointEventGroupDb != null)
+                                        {
+                                            //DB更新
+                                            await client.GetContainer(Constant.TEAMModelOS, "Teacher").ReplaceItemAsync(jointEventGroupDb, jointEventGroupDb.id);
+                                            //通知寄送 [待模板]
+                                        }
+                                    }
+                                    catch (Exception e) { }
+                                }
                             }
                             catch (Exception e)
                             {
@@ -554,6 +580,31 @@ namespace TEAMModelOS.CosmosDBTriggers
                                     await client.GetContainer(Constant.TEAMModelOS, "Common").ReplaceItemAsync<ExamInfo>(info, info.id, new PartitionKey(info.code));
                                 }
                                 await SetLearnRecordContent(info, data, _azureStorage, _azureCosmos);
+
+                                //統測活動特別處理
+                                ///活動行進狀態更新
+                                if (!string.IsNullOrWhiteSpace(info.jointExamId))
+                                {
+                                    try
+                                    {
+                                        //取得統測評量活動
+                                        JointExam jointExam = await client.GetContainer(Constant.TEAMModelOS, "Common").ReadItemAsync<JointExam>(info.jointExamId, new PartitionKey("JointExam"));
+                                        string jointEventId = jointExam.jointEventId;
+                                        string jointScheduleId = jointExam.jointScheduleId;
+                                        string jointGroupId = jointExam.jointGroupId;
+                                        string creatorId = info.creatorId;
+                                        JointEvent jointEvent = await client.GetContainer(Constant.TEAMModelOS, "Teacher").ReadItemAsync<JointEvent>(jointEventId, new PartitionKey("JointEvent"));
+                                        JointEventSchedule schedule = jointEvent.schedule.First(s => s.id.Equals(jointScheduleId));
+                                        JointEventGroupDb jointEventGroupDb = await JointService.CalJointCourseGroupScheduleStatusAsync(client, jointEventId, jointGroupId, creatorId, null, schedule);
+                                        if (jointEventGroupDb != null)
+                                        {
+                                            //DB更新
+                                            await client.GetContainer(Constant.TEAMModelOS, "Teacher").ReplaceItemAsync(jointEventGroupDb, jointEventGroupDb.id);
+                                            //通知寄送 [待模板]
+                                        }
+                                    }
+                                    catch (Exception e) { }
+                                }
                             }
                             catch (Exception e)
                             {

+ 15 - 0
TEAMModelOS.SDK/Models/Cosmos/Teacher/JointEvent.cs

@@ -92,6 +92,21 @@ namespace TEAMModelOS.SDK.Models
             public string id { get; set; }
             public string name { get; set; }
             public string no { get; set; } //系統安排的流水號 ※各組(jointGroup)的班級從1開始++ 決賽才記入
+            public List<JointEventGroupCourseGroupSchedule> schedule { get; set; } = new();
+            public class JointEventGroupCourseGroupSchedule
+            {
+                public string id { get; set; }
+                //進行狀況 undo:未開始、doing:進行中、complete:已完成、disqualify:無資格
+                public string status { get; set; }
+                public List<JointEventGroupCourseGroupScheduleSend> send { get; set; } = new();
+                public class JointEventGroupCourseGroupScheduleSend
+                {
+                    //代號 [例]start:這個階段開始(例如熱身賽開始)、end
+                    public string code { get; set; }
+                    //已發送的通知 notify:端外通知 sms:簡訊通知 email:EMail
+                    public List<string> type { get; set; } = new();
+                }
+            }
         }
     }
     /// <summary>

+ 235 - 0
TEAMModelOS.SDK/Models/Service/JointService.cs

@@ -585,6 +585,241 @@ namespace TEAMModelOS.SDK.Models.Service
 
         }
 
+        /// <summary>
+        /// 計算統測活動各報名班級的各活動階段進行狀況
+        /// </summary>
+        /// <param name="jointEventId"></param>
+        /// <param name="jointGroupId"></param>
+        /// <param name="creatorId"></param>
+        /// <param name="jointEventGroupDb">要取得的報名課程,若給null則會從DB取</param>
+        /// <param name="schedule">若給null則所有schedule的status(報名、熱身賽、決賽)都計算</param>
+        /// <param name="update">是否更新DB true:更新 false:不更新</param>
+        public static async Task<JointEventGroupDb> CalJointCourseGroupScheduleStatusAsync(CosmosClient client, string jointEventId, string jointGroupId, string creatorId, JointEventGroupDb jointEventGroupDb = null, JointEventSchedule schedule = null)
+        {
+            long now = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds();
+            string scope = "private"; //先只指定個人
+            string container = (scope.Equals("school")) ? Constant.School : Constant.Teacher;
+            //取得統測活動
+            JointEvent jointEvent = await client.GetContainer(Constant.TEAMModelOS, "Teacher").ReadItemAsync<JointEvent>(jointEventId, new PartitionKey("JointEvent"));
+            if (jointEvent == null)
+            {
+                return null;
+            }
+            //取得報名名單
+            if (jointEventGroupDb == null)
+            {
+                StringBuilder stringBuilder = new($"SELECT * FROM c WHERE c.jointEventId = '{jointEventId}' AND c.jointGroupId = '{jointGroupId}' AND c.creatorId = '{creatorId}' AND c.type = 'regular' ");
+                await foreach (var item in client.GetContainer(Constant.TEAMModelOS, container).GetItemQueryStreamIteratorSql(queryText: stringBuilder.ToString(), requestOptions: new QueryRequestOptions() { PartitionKey = new PartitionKey("JointCourse") }))
+                {
+                    using var json = await JsonDocument.ParseAsync(item.Content);
+                    if (json.RootElement.TryGetProperty("_count", out JsonElement count) && count.GetUInt16() > 0)
+                    {
+                        foreach (var obj in json.RootElement.GetProperty("Documents").EnumerateArray())
+                        {
+                            jointEventGroupDb = obj.ToObject<JointEventGroupDb>();
+                        }
+                    }
+                }
+            }
+            if (jointEventGroupDb == null)
+            {
+                return null;
+            }
+            //計算各階段Status
+            List<JointEventSchedule> jointScheduleList = new List<JointEventSchedule>(); //要計算的活動階段
+            if (schedule != null) jointScheduleList.Add(schedule);
+            else jointScheduleList = jointEvent.schedule;
+            ///計算邏輯
+            foreach (JointEventGroupBase.JointEventGroupCourse courseRow in jointEventGroupDb.courseLists) //各報名課程
+            {
+                string courseId = courseRow.courseId;
+                foreach (JointEventGroupBase.JointEventGroupCourseGroup courseGroup in courseRow.groupLists) //各報名課程名單(各班)
+                {
+                    string groupId = courseGroup.id;
+                    foreach (JointEventSchedule eventSchedule in jointScheduleList) //各活動階段
+                    {
+                        //活動階段篩選:報名、熱身賽、決賽 才記入
+                        if(eventSchedule.type.Equals("join") || eventSchedule.type.Equals("exam"))
+                        {
+                            JointEventGroupBase.JointEventGroupCourseGroup.JointEventGroupCourseGroupSchedule courseGroupSchedule = courseGroup.schedule.FirstOrDefault(s => s.id.Equals(eventSchedule.id));
+                            if (courseGroupSchedule == null) //不存在 > 新建
+                            {
+                                courseGroupSchedule = new JointEventGroupBase.JointEventGroupCourseGroup.JointEventGroupCourseGroupSchedule()
+                                {
+                                    id = eventSchedule.id,
+                                    status = "undo"
+                                };
+                                courseGroup.schedule.Add(courseGroupSchedule);
+                                courseGroupSchedule = courseGroup.schedule.FirstOrDefault(s => s.id.Equals(eventSchedule.id));
+                            }
+                            courseGroupSchedule.status = await GetGroupJointScheduleStatus(client, jointEventId, jointGroupId, groupId, eventSchedule, jointEventGroupDb, creatorId);
+                        }
+                    }
+                }
+            }
+
+            return jointEventGroupDb;
+        }
+
+        //判斷某groupId在某JointSchedule是否完成
+        /// <summary>
+        /// 判斷某groupId在某JointSchedule是否完成
+        /// </summary>
+        /// <param name="jointEventId"></param>
+        /// <param name="jointGroupId"></param>
+        /// <param name="groupId"></param>
+        /// <param name="schedule"></param>
+        /// <param name="jointCourse">統測報名班級DB document 若為null則由DB取</param>
+        /// <param name="creatorId"></param>
+        /// <returns></returns>
+        private static async Task<string> GetGroupJointScheduleStatus(CosmosClient client, string jointEventId, string jointGroupId, string groupId, JointEventSchedule schedule, JointEventGroupDb jointCourse, string creatorId)
+        {
+            long now = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds();
+            string result = "undo";
+            string scope = "private"; //先只指定個人
+            string container = (scope.Equals("school")) ? Constant.School : Constant.Teacher;
+            if (schedule.startTime <= now && now <= schedule.endTime) //活動階段在進行中才續行做Status判斷
+            {
+                switch (schedule.type)
+                {
+                    //報名
+                    ///算出邏輯:(1)可取得該報名課程名單 -> complete (2)無法取得 -> undo
+                    case "join":
+                        if (jointCourse == null)
+                        {
+                            StringBuilder stringBuilderJointCourse = new($"SELECT * FROM c WHERE c.jointEventId = '{jointEventId}' AND c.jointGroupId = '{jointGroupId}' AND c.creatorId = '{creatorId}' AND c.type = 'regular' ");
+                            await foreach (var item in client.GetContainer(Constant.TEAMModelOS, container).GetItemQueryStreamIteratorSql(queryText: stringBuilderJointCourse.ToString(), requestOptions: new QueryRequestOptions() { PartitionKey = new PartitionKey("JointCourse") }))
+                            {
+                                using var json = await JsonDocument.ParseAsync(item.Content);
+                                if (json.RootElement.TryGetProperty("_count", out JsonElement count) && count.GetUInt16() > 0)
+                                {
+                                    foreach (var obj in json.RootElement.GetProperty("Documents").EnumerateArray())
+                                    {
+                                        jointCourse = obj.ToObject<JointEventGroupDb>();
+                                    }
+                                }
+                            }
+                        }
+                        if (jointCourse == null)
+                        {
+                            break;
+                        }
+                        foreach (JointEventGroupBase.JointEventGroupCourse courseInfo in jointCourse.courseLists)
+                        {
+                            JointEventGroupBase.JointEventGroupCourseGroup groupExist = courseInfo.groupLists.FirstOrDefault(g => g.id.Equals(groupId));
+                            if (groupExist != null)
+                            {
+                                result = "complete";
+                            }
+                        }
+                        break;
+                    //競賽 
+                    ///算出邏輯: (1)無法取得報名班級(決賽班級) -> 資格不符(disqualify) 
+                    ///         (2)統測評量數 > 0 && 個人評量完成數(學生有一人有作答則是為完成) == 統測評量數 -> 完成(complete)
+                    ///         (3)統測評量數 > 0 && 個人評量完成數 > 0 && 統測評量數 > 個人評量完成數 -> 進行中(doing)
+                    ///         (4)default -> undo
+                    case "exam":
+                        //取得老師報名課程或決賽老師課程
+                        List<JointEventGroupDb> jointEventGroup = new List<JointEventGroupDb>(); //個人課程
+                        StringBuilder stringBuilderEventGroup = new($"SELECT * FROM c WHERE c.jointEventId = '{jointEventId}' AND c.jointGroupId = '{jointGroupId}' ");
+                        if (schedule.examType.Equals("regular")) //熱身賽
+                        {
+                            stringBuilderEventGroup.Append($" AND (c.type = 'regular' OR NOT IS_DEFINED(c.type) OR IS_NULL(c.type)) ");
+                        }
+                        else if (schedule.examType.Equals("custom")) //決賽
+                        {
+                            stringBuilderEventGroup.Append($" AND c.type = 'custom' AND c.jointScheduleId = '{schedule.id}' ");
+                        }
+                        if (!string.IsNullOrWhiteSpace(creatorId))
+                        {
+                            stringBuilderEventGroup.Append($" AND c.creatorId = '{creatorId}' ");
+                        }
+                        await foreach (var item in client.GetContainer(Constant.TEAMModelOS, Constant.Teacher).GetItemQueryStreamIteratorSql(queryText: stringBuilderEventGroup.ToString(), requestOptions: new QueryRequestOptions() { PartitionKey = new PartitionKey("JointCourse") }))
+                        {
+                            using var json = await JsonDocument.ParseAsync(item.Content);
+                            if (json.RootElement.TryGetProperty("_count", out JsonElement count) && count.GetUInt16() > 0)
+                            {
+                                foreach (var obj in json.RootElement.GetProperty("Documents").EnumerateArray())
+                                {
+                                    jointEventGroup.Add(obj.ToObject<JointEventGroupDb>());
+                                }
+                            }
+                        }
+                        //取得本Schedule的所有JointExam
+                        List<string> jointExamIdList = new List<string>();
+                        StringBuilder stringBuilderJointExam = new($"SELECT DISTINCT VALUE c.id FROM c WHERE c.jointEventId = '{jointEventId}' AND c.jointGroupId = '{jointGroupId}' AND c.jointScheduleId = '{schedule.id}' ");
+                        var resultJointExam = await client.GetContainer(Constant.TEAMModelOS, Constant.Common).GetList<string>(stringBuilderJointExam.ToString(), $"JointExam");
+                        if (resultJointExam.list.IsNotEmpty())
+                        {
+                            jointExamIdList = new List<string>(resultJointExam.list);
+                        }
+                        //取得所有個人評量
+                        List<string> examIdList = new List<string>();
+                        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-')";
+                        await foreach (var item in client.GetContainer(Constant.TEAMModelOS, Constant.Common).GetItemQueryIteratorSql<string>(queryText: sqlExam, requestOptions: new QueryRequestOptions { }))
+                        {
+                            examIdList.Add(item);
+                        }
+                        //取得所有考試的作答結果
+                        List<string> finishExamIdList = new List<string>();
+                        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')";
+                        await foreach (var item in client.GetContainer(Constant.TEAMModelOS, Constant.Common).GetItemQueryStreamIteratorSql(queryText: sqlExamClassResult, requestOptions: new QueryRequestOptions() { }))
+                        {
+                            using var json = await JsonDocument.ParseAsync(item.Content);
+                            if (json.RootElement.TryGetProperty("_count", out JsonElement count) && count.GetUInt16() > 0)
+                            {
+                                foreach (var obj in json.RootElement.GetProperty("Documents").EnumerateArray())
+                                {
+                                    string examId = obj.GetProperty("examId").ToString();
+                                    string classId = obj.GetProperty("classId").ToString();
+                                    List<List<string>> studentAnswers = obj.GetProperty("studentAnswers").ToObject<List<List<string>>>();
+                                    bool isFinish = false; //評量是否已完成 ※有任一學生有作答則視為已完成
+                                    foreach (List<string> studentAnswer in studentAnswers)
+                                    {
+                                        if (studentAnswer.Count > 0) { isFinish = true; break; }
+                                    }
+                                    if (isFinish)
+                                    {
+                                        finishExamIdList.Add(examId);
+                                    }
+                                }
+                            }
+                        }
+                        //結果判斷
+                        JointEventGroupBase.JointEventGroupCourseGroup classInGroup = new JointEventGroupBase.JointEventGroupCourseGroup(); //取得該班級的報名/決賽資訊
+                        foreach (JointEventGroupDb eventGroup in jointEventGroup)
+                        {
+                            foreach (JointEventGroupBase.JointEventGroupCourse eventCourse in eventGroup.courseLists)
+                            {
+                                foreach (JointEventGroupBase.JointEventGroupCourseGroup group in eventCourse.groupLists)
+                                {
+                                    if (group.id.Equals(groupId))
+                                    {
+                                        classInGroup = group;
+                                    }
+                                }
+                            }
+                        }
+                        if (jointEventGroup.Count.Equals(0) || string.IsNullOrWhiteSpace(classInGroup.id)) //資格不符
+                        {
+                            result = "disqualify";
+                        }
+                        else if (jointExamIdList.Count > 0 && jointExamIdList.Count.Equals(finishExamIdList.Count))
+                        {
+                            result = "complete";
+                        }
+                        else if (jointExamIdList.Count > 0 && finishExamIdList.Count > 0 && jointExamIdList.Count > finishExamIdList.Count)
+                        {
+                            result = "doing";
+                        }
+                        break;
+                }
+            }
+
+            return result;
+        }
+
+
         /// <summary>
         /// 計算決賽通過的老師課程名單用中間model
         /// </summary>

+ 2 - 2
TEAMModelOS/ClientApp/src/common/BaseLayout.vue

@@ -1623,7 +1623,7 @@
 								name: this.$t("system.menu.unifiedPurchasingPlatform"),
 								router: "",
 								role: "",
-                                permission: "admin-joint-event|joint-exam-mark",
+                                permission: "admin-joint-event|manager-joint-event|joint-exam-mark",
 								subName: "unifiedPurchasingPlatform",
                                 isShow: this.IES5Menu && this.isGlobalSite,
 								child: [
@@ -1633,7 +1633,7 @@
 										router: "/home/htMgtHome",
 										tag: "",
 										role: "",
-                                        permission: "admin-joint-event",
+                                        permission: "admin-joint-event|manager-joint-event",
 										menuName: "htMgtHome",
 										isShow: true
 									},

+ 22 - 3
TEAMModelOS/Controllers/Teacher/InitController.cs

@@ -1339,12 +1339,31 @@ namespace TEAMModelOS.Controllers
                 var id = jwt.Payload.Sub;
                 var client = _azureCosmos.GetCosmosClient();
                 long now = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds();
-                //取得個人權限
+                //取得個人權限 admin-joint-event:統測活動大總管
                 Teacher teacher = await client.GetContainer(Constant.TEAMModelOS, "Teacher").ReadItemAsync<Teacher>(id, new PartitionKey("Base"));
                 permission = teacher.permission;
-                //活動閱卷權限
+                //取得各活動管理員 manager-joint-event
+                bool hasManager = false;
+                string jointManagerSql = $"SELECT c.id as jointEventId, c.name as jointEventName FROM c WHERE ARRAY_CONTAINS(c.admin,'{id}') ";
+                await foreach (var item in client.GetContainer(Constant.TEAMModelOS, "Teacher").GetItemQueryStreamIteratorSql(queryText: jointManagerSql, requestOptions: new QueryRequestOptions() { PartitionKey = new PartitionKey($"JointEvent") }))
+                {
+                    using var json = await JsonDocument.ParseAsync(item.Content);
+                    if (json.RootElement.TryGetProperty("_count", out JsonElement count) && count.GetUInt16() > 0)
+                    {
+                        foreach (var obj in json.RootElement.GetProperty("Documents").EnumerateArray())
+                        {
+                            hasManager = true;
+                            break;
+                        }
+                    }
+                }
+                if (hasManager && !permission.Contains("manager-joint-event"))
+                {
+                    permission.Add("manager-joint-event");
+                }
+                //活動閱卷權限 joint-exam-mark
                 bool hasAssistant = false;
-                string jointScheduleSql = $"SELECT c.id as jointEventId, c.name as jointEventName, s.id as jointScheduleId, s.name as jointScheduleName, s.startTime, s.endTime, g.id as jointGroupId, g.name as jointGroupName, g.assistants FROM c JOIN s IN c.schedule JOIN g IN c.groups WHERE s.startTime <= {now} AND s.endTime > {now} AND s.type = 'exam' AND array_contains(g.assistants,'{id}') ";
+                string jointScheduleSql = $"SELECT c.id as jointEventId, c.name as jointEventName, s.name as jointScheduleName, s.startTime, s.endTime, g.id as jointGroupId, g.name as jointGroupName, g.assistants FROM c JOIN s IN c.schedule JOIN g IN c.groups WHERE s.startTime <= {now} AND s.endTime > {now} AND s.type = 'exam' AND array_contains(g.assistants,'{id}') ";
                 await foreach (var item in client.GetContainer(Constant.TEAMModelOS, "Teacher").GetItemQueryStreamIteratorSql(queryText: jointScheduleSql, requestOptions: new QueryRequestOptions() { PartitionKey = new PartitionKey($"JointEvent") }))
                 {
                     using var json = await JsonDocument.ParseAsync(item.Content);

+ 16 - 143
TEAMModelOS/Controllers/Teacher/JointEventController.cs

@@ -90,6 +90,13 @@ namespace TEAMModelOS.Controllers.Common
                 if(string.IsNullOrWhiteSpace(tmid)) return BadRequest();
                 long now = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds();
                 var client = _azureCosmos.GetCosmosClient();
+                //取得個人權限
+                bool hasAdmin = false;
+                Teacher teacher = await client.GetContainer(Constant.TEAMModelOS, "Teacher").ReadItemAsync<Teacher>(tmid, new PartitionKey("Base"));
+                List<string> permission = teacher.permission;
+                if (permission.Contains("admin-joint-event")) hasAdmin = true;
+
+                //取得統測活動
                 StringBuilder stringBuilderSelect = new($"SELECT * FROM c WHERE 1=1 ");
                 StringBuilder stringBuilderWhere = new();
                 string jointEventId = (request.TryGetProperty("jointEventId", out JsonElement _jointEventId)) ? _jointEventId.ToString() : string.Empty;
@@ -171,6 +178,11 @@ namespace TEAMModelOS.Controllers.Common
                         stringBuilderWhere.Append(" AND " + whereGeo);
                     }
                 }
+                //根據權限取得活動 ※非管理者只能取得JointEvent.admin裡面有自己ID的活動
+                if (!hasAdmin)
+                {
+                    stringBuilderWhere.Append($" AND ARRAY_CONTAINS(c.admin,'{tmid}') ");
+                }
                 List<JointEventDto> eventInfo = new List<JointEventDto>();
                 StringBuilder sql = stringBuilderSelect.Append(stringBuilderWhere);
                 await foreach (var item in client.GetContainer(Constant.TEAMModelOS, "Teacher").GetItemQueryStreamIteratorSql(queryText: sql.ToString(), requestOptions: new QueryRequestOptions() { PartitionKey = new PartitionKey("JointEvent") }))
@@ -438,7 +450,7 @@ namespace TEAMModelOS.Controllers.Common
                     {
                         jointEvent.schedule.Add(jointEventSchedule);
                     }
-                    //製作Schedule progress更新訊息(active-task)
+                    //製作Schedule progress更新訊息(active-task) ※時間到觸發流程開始結束用
                     bool sendScheduleMsg = (!startTimeOld.Equals(jointEventSchedule.startTime) || !endTimeOld.Equals(jointEventSchedule.endTime) ) ? true : false; //是否發送變更Schedule.progress訊息
                     if(sendScheduleMsg)
                     {
@@ -579,21 +591,6 @@ namespace TEAMModelOS.Controllers.Common
                         foreach (var obj in json.RootElement.GetProperty("Documents").EnumerateArray())
                         {
                             JointEventGroupDto jointEventGroupDto = obj.ToObject<JointEventGroupDto>();
-                            foreach(JointEventGroupDto.JointEventGroupCourseDto course in jointEventGroupDto.courseLists)
-                            {
-                                foreach(JointEventGroupDto.JointEventGroupCourseGroupDto group in course.groupLists)
-                                {
-                                    foreach (JointEventSchedule schedule in jointEvent.schedule)
-                                    {
-                                        string scheduleStatus = await GetGroupFinishJointSchedule(jointEventGroupDto.jointEventId, jointEventGroupDto.jointGroupId, group.id, schedule, creatorId);
-                                        group.schedule.Add(new {id = schedule.id, name = schedule.name, status = scheduleStatus });
-                                    }
-                                    if(!groupIdList.Contains(group.id))
-                                    {
-                                        groupIdList.Add(group.id);
-                                    }
-                                }
-                            }
                             JointCourseDto.Add(jointEventGroupDto);
                         }
                     }
@@ -771,6 +768,9 @@ namespace TEAMModelOS.Controllers.Common
                         }
                     }
                     jointCourse.courseLists = courseListsFix;
+                    //各Schedule Status計算
+                    jointCourse = await JointService.CalJointCourseGroupScheduleStatusAsync(client, jointEventId, jointGroupId, creatorId, jointCourse, null);
+                    //報名班級 DB更新
                     await client.GetContainer(Constant.TEAMModelOS, "Teacher").UpsertItemAsync<JointEventGroupDb>(jointCourse, new PartitionKey("JointCourse"));
                 }
 
@@ -1314,133 +1314,6 @@ namespace TEAMModelOS.Controllers.Common
             return Ok(sasList);
         }
 
-        //判斷某groupId在某JointSchedule是否完成
-        private async Task<string> GetGroupFinishJointSchedule(string jointEventId, string jointGroupId, string groupId, JointEventSchedule schedule, string creatorId)
-        {
-            string result = "undo";
-            var client = _azureCosmos.GetCosmosClient();
-            string scope = "private"; //先只指定個人
-            string container = (scope.Equals("school")) ? Constant.School : Constant.Teacher;
-            switch (schedule.type)
-            {
-                //報名
-                case "join":
-                    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}' ");
-                    await foreach (var item in client.GetContainer(Constant.TEAMModelOS, container).GetItemQueryStreamIteratorSql(queryText: stringBuilder.ToString(), requestOptions: new QueryRequestOptions() { PartitionKey = new PartitionKey("JointCourse") }))
-                    {
-                        using var json = await JsonDocument.ParseAsync(item.Content);
-                        if (json.RootElement.TryGetProperty("_count", out JsonElement count) && count.GetUInt16() > 0)
-                        {
-                            foreach (var obj in json.RootElement.GetProperty("Documents").EnumerateArray())
-                            {
-                                if (obj.GetProperty("id").GetString().Equals(groupId))
-                                {
-                                    result = "complete";
-                                }
-                            }
-                        }
-                    }
-                    break;
-                //競賽
-                case "exam":
-                    //取得老師報名課程或決賽老師課程
-                    List<JointEventGroupDb> jointEventGroup = new List<JointEventGroupDb>(); //個人課程
-                    StringBuilder stringBuilderEventGroup = new($"SELECT * FROM c WHERE c.jointEventId = '{jointEventId}' AND c.jointGroupId = '{jointGroupId}' ");
-                    if (schedule.examType.Equals("regular")) //熱身賽
-                    {
-                        stringBuilderEventGroup.Append($" AND (c.type = 'regular' OR NOT IS_DEFINED(c.type) OR IS_NULL(c.type)) ");
-                    }
-                    else if(schedule.examType.Equals("custom")) //決賽
-                    {
-                        stringBuilderEventGroup.Append($" AND c.type = 'custom' AND c.jointScheduleId = '{schedule.id}' ");
-                    }
-                    if(!string.IsNullOrWhiteSpace(creatorId))
-                    {
-                        stringBuilderEventGroup.Append($" AND c.creatorId = '{creatorId}' ");
-                    }
-                    await foreach (var item in client.GetContainer(Constant.TEAMModelOS, Constant.Teacher).GetItemQueryStreamIteratorSql(queryText: stringBuilderEventGroup.ToString(), requestOptions: new QueryRequestOptions() { PartitionKey = new PartitionKey("JointCourse") }))
-                    {
-                        using var json = await JsonDocument.ParseAsync(item.Content);
-                        if (json.RootElement.TryGetProperty("_count", out JsonElement count) && count.GetUInt16() > 0)
-                        {
-                            foreach (var obj in json.RootElement.GetProperty("Documents").EnumerateArray())
-                            {
-                                jointEventGroup.Add(obj.ToObject<JointEventGroupDb>());
-                            }
-                        }
-                    }
-                    //取得本Schedule的所有JointExam
-                    List<string> jointExamIdList = new List<string>();
-                    StringBuilder stringBuilderJointExam = new($"SELECT DISTINCT VALUE c.id FROM c WHERE c.jointEventId = '{jointEventId}' AND c.jointGroupId = '{jointGroupId}' AND c.jointScheduleId = '{schedule.id}' ");
-                    var resultJointExam = await _azureCosmos.GetCosmosClient().GetContainer(Constant.TEAMModelOS, Constant.Common).GetList<string>(stringBuilderJointExam.ToString(), $"JointExam");
-                    if (resultJointExam.list.IsNotEmpty())
-                    {
-                        jointExamIdList = new List<string>(resultJointExam.list);
-                    }
-                    //取得所有個人評量
-                    List<string> examIdList = new List<string>();
-                    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-')";
-                    await foreach (var item in client.GetContainer(Constant.TEAMModelOS, Constant.Common).GetItemQueryIteratorSql<string>(queryText: sqlExam, requestOptions: new QueryRequestOptions { }))
-                    {
-                        examIdList.Add(item);
-                    }
-                    //取得所有考試的作答結果
-                    List<string> finishExamIdList = new List<string>();
-                    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')";
-                    await foreach (var item in client.GetContainer(Constant.TEAMModelOS, Constant.Common).GetItemQueryStreamIteratorSql(queryText: sqlExamClassResult, requestOptions: new QueryRequestOptions() { }))
-                    {
-                        using var json = await JsonDocument.ParseAsync(item.Content);
-                        if (json.RootElement.TryGetProperty("_count", out JsonElement count) && count.GetUInt16() > 0)
-                        {
-                            foreach (var obj in json.RootElement.GetProperty("Documents").EnumerateArray())
-                            {
-                                string examId = obj.GetProperty("examId").ToString();
-                                string classId = obj.GetProperty("classId").ToString();
-                                List<List<string>> studentAnswers = obj.GetProperty("studentAnswers").ToObject<List<List<string>>>();
-                                bool isFinish = false; //評量是否已完成 ※有任一學生有作答則視為已完成
-                                foreach (List<string> studentAnswer in studentAnswers)
-                                {
-                                    if (studentAnswer.Count > 0) { isFinish = true; break; }
-                                }
-                                if (isFinish)
-                                {
-                                    finishExamIdList.Add(examId);
-                                }
-                            }
-                        }
-                    }
-                    //結果判斷
-                    JointEventGroupBase.JointEventGroupCourseGroup classInGroup = new JointEventGroupBase.JointEventGroupCourseGroup(); //取得該班級的報名/決賽資訊
-                    foreach(JointEventGroupDb eventGroup in jointEventGroup)
-                    {
-                        foreach(JointEventGroupBase.JointEventGroupCourse eventCourse in eventGroup.courseLists)
-                        {
-                            foreach (JointEventGroupBase.JointEventGroupCourseGroup group in eventCourse.groupLists)
-                            {
-                                if(group.id.Equals(groupId))
-                                {
-                                    classInGroup = group;
-                                }
-                            }
-                        }
-                    }
-                    if (jointEventGroup.Count.Equals(0) || string.IsNullOrWhiteSpace(classInGroup.id)) //資格不符
-                    {
-                        result = "disqualify";
-                    }
-                    else if (jointExamIdList.Count > 0 && jointExamIdList.Count.Equals(finishExamIdList.Count))
-                    {
-                        result = "complete";
-                    }
-                    else if (jointExamIdList.Count > 0 && finishExamIdList.Count > 0 && jointExamIdList.Count > finishExamIdList.Count)
-                    {
-                        result = "doing";
-                    }
-                    break;
-            }
-            return result;
-        }
-
         //生成決賽名單
         [ProducesDefaultResponseType]
 #if !DEBUG