ソースを参照

統測架構更新

jeff 11 ヶ月 前
コミット
a0af92b42f

+ 24 - 30
TEAMModelOS.SDK/Models/Cosmos/Teacher/JointExam.cs

@@ -11,47 +11,41 @@ namespace TEAMModelOS.SDK.Models
         public string name { get; set; }
         public JointEventGeo geo { get; set; } = new JointEventGeo(); //可參加活動的國省市區限制
         public List<string> admin { get; set; } = new List<string>(); //管理者
-        public List<JointEventGroup> groups { get; set; } = new List<JointEventGroup>(); //群組
-        public List<JointEventSchedule> schedule { get; set; } = new List<JointEventSchedule>(); //進程
+        public List<JointEventGroup> groups { get; set; } = new List<JointEventGroup>(); //群組列表
+        public List<JointEventSchedule> schedule { get; set; } = new List<JointEventSchedule>(); //進程列表
         public long createTime { get; set; }
         public long startTime { get; set; }
         public long endTime { get; set; }
         public string progress { get; set; } //進程 "pending" "going" "finish"
-        public class JointEventGroup
+        public class JointEventGroup //活動群組
         {
-            public string id { get; set; }
-            public string name { get; set; }
+            public string id { get; set; } //活動群組ID
+            public string name { get; set; } //群組名稱
             public HashSet<string> assistants { get; set; } = new HashSet<string>(); //評審TMID列表
         }
-        public class JointEventPaper
+        public class JointEventGeo //活動區域(國省市區)
         {
-            public string code { get; set; }
-            public string id { get; set; }
-            public string blob { get; set; }
-            public string name { get; set; }
+            public string countryId { get; set; } //國
+            public string provinceId { get; set; } //省
+            public string cityId { get; set; } //市
+            public string distId { get; set; } //區
         }
-        public class JointEventGeo
+        public class JointEventSchedule //活動進程
         {
-            public string countryId { get; set; }
-            public string provinceId { get; set; }
-            public string cityId { get; set; }
-            public string distId { get; set; }
-        }
-        public class JointEventSchedule
-        {
-            public string type { get; set; } //"exam":評量 "join":報名
+            public string id { get; set; } //活動進程ID
+            public string name { get; set; } //進程名稱
+            public string type { get; set; } //"exam":評量 "join":報名 "other":其他
             public string examType { get; set; } //評量類型 "regular":一般競賽 "custom":挑戰賽
             public bool examOverwrite { get; set; } //評量可否重複作答 true:可 false:不可
-            public string id { get; set; } 
-            public string name { get; set; }
             public long startTime { get; set; }
             public long endTime { get; set; }
-            public string progress { get; set; } //進程 "pending" "going" "finish"
+            public string progress { get; set; } //進行狀況 "pending" "going" "finish"
+            public int orderby { get; set; } //排序
         }
     }
 
     /// <summary>
-    /// 統測活動老師個人課程 基底class
+    /// 統測活動老師報名課程 基底class
     /// </summary>
     public class JointEventGroupBase
     {
@@ -75,7 +69,7 @@ namespace TEAMModelOS.SDK.Models
         }
     }
     /// <summary>
-    /// 統測活動老師課程 CosmosDB ※id = jointEventId + TMID 老師報名後記入這裡
+    /// 統測活動老師報名課程 CosmosDB用 ※jointEventId、jointGroupId、creatorId 三個欄位合為主Key
     /// </summary>
     public class JointEventGroupDb : JointEventGroupBase
     {
@@ -89,7 +83,7 @@ namespace TEAMModelOS.SDK.Models
     }
 
     /// <summary>
-    /// 統測活動學校課程 基底class
+    /// 統測活動學校報名班級 基底class
     /// </summary>
     public class JointEventClassBase
     {
@@ -104,7 +98,7 @@ namespace TEAMModelOS.SDK.Models
         }
     }
     /// <summary>
-    /// 統測活動老師課程 CosmosDB ※id = jointEventId + TMID 老師報名後記入這裡
+    /// 統測活動學校報名班級 CosmosDB [待做]
     /// </summary>
     public class JointEventClassDb : JointEventClassBase
     {
@@ -129,16 +123,16 @@ namespace TEAMModelOS.SDK.Models
         public string name { get; set; }
         public string examType { get; set; } //評量類型 "regular":一般競賽 "custom":挑戰賽
         public bool examOverwrite { get; set; } //評量可否重複作答 true:可 false:不可
-        public List<JointEventClassBase> classes { get; set; } = new();
-        public List<JointEventGroupBase> stuLists { get; set; } = new();
+        public List<JointEventClassBase> classes { get; set; } = new(); //班級列表 ※examType="custom"才填入,其餘評量班級從報名班級(DB)取得
+        public List<JointEventGroupBase> stuLists { get; set; } = new(); //個人課程列表 ※examType="custom"才填入,其餘評量班級從老師報名課程(DB)取得
         public List<PaperSimple> papers { get; set; } = new();
-        public string progress { get; set; }
+        public string progress { get; set; } //進行狀況 "pending" "going" "finish"
         public long createTime { get; set; }
         public long updateTime { get; set; }
         public long startTime { get; set; }
         public long endTime { get; set; }
         public string source { get; set; } //评测类型  0:'線上自主評量', 1:'智慧教室評量', 2:'卷卡合一評量'
-        public string scope { get; set; }
+        public string scope { get; set; } 
     }
 
 }

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

@@ -0,0 +1,264 @@
+using Azure.Messaging.ServiceBus;
+using Azure.Storage.Blobs.Models;
+using Azure;
+using Microsoft.Azure.Cosmos;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Security.Cryptography;
+using System.Text;
+using System.Text.Json;
+using System.Threading.Tasks;
+using TEAMModelOS.SDK.DI;
+using TEAMModelOS.SDK.Extension;
+using TEAMModelOS.SDK.Models.Service.BI;
+using TEAMModelOS.SDK.Services;
+using static TEAMModelOS.SDK.Models.JointEventGroupBase;
+using Azure.Core;
+using Microsoft.Extensions.Configuration;
+
+namespace TEAMModelOS.SDK.Models.Service
+{
+    public static class JointService
+    {
+        //取得JointExam生成Exam
+        public static async Task GenerateExamFromJointAsync(CosmosClient client, AzureStorageFactory _azureStorage, AzureServiceBusFactory _serviceBus, CoreAPIHttpService _coreAPIHttpService, AzureRedisFactory _azureRedis, IConfiguration _configuration, DingDing _dingDing, string jointExamId)
+        {
+            try
+            {
+                long now = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds();
+                //取得JointExam
+                List<JointEventClassBase> classes = new List<JointEventClassBase>();
+                List<JointEventGroupBase> stuLists = new List<JointEventGroupBase>();
+                JointExam jointExam = await client.GetContainer(Constant.TEAMModelOS, "Common").ReadItemAsync<JointExam>(jointExamId, new PartitionKey("JointExam"));
+                //取得JointCourse ※examType == "custom" 之後再處理
+                List<JointEventGroupDb> jointCourses = new List<JointEventGroupDb>();
+                if (!jointExam.examType.Equals("custom"))
+                {
+                    string jointCourseSql = $"SELECT * FROM c WHERE c.jointEventId = '{jointExam.jointEventId}' AND c.jointGroupId = '{jointExam.jointGroupId}'";
+                    await foreach (var item in client.GetContainer("TEAMModelOS", "Teacher").GetItemQueryIteratorSql<JointEventGroupDb>(queryText: jointCourseSql, requestOptions: new QueryRequestOptions() { PartitionKey = new PartitionKey($"JointCourse") }))
+                    {
+                        jointCourses.Add(item);
+                    }
+                }
+                //評量資料生成 ExamInfo actExamInfo ※一個課程一個Exam
+                List<ExamInfo> examList = new List<ExamInfo>();
+                foreach (JointEventGroupDb jointCourse in jointCourses)
+                {
+                    string actExamCreatorId = jointCourse.creatorId;
+                    //個人課程
+                    if(jointCourse.scope.Equals("private"))
+                    {
+                        foreach (JointEventGroupCourse jointExamGroupCourse in jointCourse.courseLists)
+                        {
+                            string actExamCourseId = jointExamGroupCourse.courseId;
+                            string actExamCourseName = jointExamGroupCourse.courseName;
+                            //評量資料生成
+                            ExamInfo actExamInfo = new ExamInfo();
+                            ///取得已生成的Exam ※
+                            string examSql = $"SELECT DISTINCT c.id, c.source, c.name, c.jointExamId, c.subjects, c.stuLists, c.targets, c.papers, c.year, c.startTime, c.endTime FROM c JOIN s IN c.subjects WHERE c.jointExamId = '{jointExam.id}' AND s.id = '{actExamCourseId}'";
+                            await foreach (var item in client.GetContainer("TEAMModelOS", "Teacher").GetItemQueryIteratorSql<ExamInfo>(queryText: examSql, requestOptions: new QueryRequestOptions() { PartitionKey = new PartitionKey($"Exam-{actExamCreatorId}") }))
+                            {
+                                actExamInfo = item;
+                            }
+                            ///尚無評量資料
+                            if (string.IsNullOrWhiteSpace(actExamInfo.id))
+                            {
+                                actExamInfo.code = $"Exam-{actExamCreatorId}";
+                                actExamInfo.owner = "teacher";
+                                actExamInfo.scope = jointCourse.scope;
+                                actExamInfo.creatorId = actExamCreatorId;
+                                actExamInfo.id = Guid.NewGuid().ToString();
+                            }
+                            actExamInfo.source = jointExam.source;
+                            actExamInfo.name = jointExam.name;
+                            actExamInfo.jointExamId = jointExam.id;
+                            actExamInfo.subjects = new List<ExamSubject>() { new ExamSubject() { id = actExamCourseId, name = actExamCourseName, classCount = jointExamGroupCourse.groupLists.Count } };
+                            ///評量stuLists
+                            foreach (JointEventGroupCourseGroup actGroup in jointExamGroupCourse.groupLists)
+                            {
+                                if(!actExamInfo.stuLists.Contains(actGroup.id))
+                                {
+                                    actExamInfo.stuLists.Add(actGroup.id);
+                                }
+                                List<string> targetRow = new List<string>() { actExamCourseId, actGroup.id };
+                                var targetRowJson = JsonSerializer.SerializeToElement(targetRow);
+                                if(!actExamInfo.targets.Contains(targetRowJson))
+                                {
+                                    actExamInfo.targets.Add(targetRowJson);
+                                }
+                            }
+                            ///試卷
+                            actExamInfo.papers = Newtonsoft.Json.JsonConvert.DeserializeObject<List<PaperSimple>>(Newtonsoft.Json.JsonConvert.SerializeObject(jointExam.papers));
+                            ///時間
+                            actExamInfo.year = DateTimeOffset.UtcNow.Year;
+                            actExamInfo.startTime = jointExam.startTime;
+                            actExamInfo.endTime = jointExam.endTime;
+
+                            examList.Add(actExamInfo);
+                        }
+                    }
+                    //[待做] 學校班級
+                }
+
+                //生成評量
+                if (examList.Count > 0)
+                {
+                    foreach (ExamInfo exam in examList)
+                    {
+                        await GenerateExam(client, _azureStorage, _serviceBus, _coreAPIHttpService, _azureRedis, _configuration, _dingDing, jointExam, exam);
+                    }
+                }
+            }
+            catch (Exception)
+            {
+                
+            }
+        }
+
+        //生成評量(單)
+        private static async Task<string> GenerateExam(CosmosClient client, AzureStorageFactory _azureStorage, AzureServiceBusFactory _serviceBus, CoreAPIHttpService _coreAPIHttpService, AzureRedisFactory _azureRedis, IConfiguration _configuration, DingDing _dingDing, JointExam jointExam, ExamInfo exam)
+        {
+            long now = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds();
+            string Result = string.Empty;
+            exam.createTime = now;
+            if (exam.startTime <= 0) exam.startTime = now;
+            List<(string pId, List<string> gid)> ps = new();
+            var group = exam.groupLists;
+            if (group.Count > 0)
+            {
+                foreach (var keys in group)
+                {
+                    foreach (KeyValuePair<string, List<string>> pp in keys)
+                    {
+                        ps.Add((pp.Key, pp.Value));
+                    }
+                }
+            }
+            List<string> classes = ExamService.getClasses(exam.classes, exam.stuLists);
+            (List<RMember> tchList, List<RGroupList> classLists) = await GroupListService.GetMemberByListids(_coreAPIHttpService, client, _dingDing, classes, exam.school, ps);
+            exam.stuCount = tchList.Count;
+            string mode = string.Empty;
+            ResponseMessage response = null;
+            if (string.IsNullOrEmpty(exam.id))
+            {
+                mode = "add";
+            }
+            else
+            {
+                response = await client.GetContainer("TEAMModelOS", "Common").ReadItemStreamAsync(exam.id, new PartitionKey($"{exam.code}"));
+                if (response.StatusCode == System.Net.HttpStatusCode.OK) mode = "upd";
+                else mode = "add";
+            }
+            //DB操作
+            if (mode.Equals("add")) //新建
+            {
+                if (string.IsNullOrWhiteSpace(exam.id)) exam.id = Guid.NewGuid().ToString();
+                exam.progress = (exam.startTime > now) ? "pending" : "going";
+                var messageBlob = new ServiceBusMessage();
+                if (exam.scope.Equals("school") && !string.IsNullOrWhiteSpace(exam.school))
+                {
+                    exam.size = await _azureStorage.GetBlobContainerClient(exam.school).GetBlobsSize($"exam/{exam.id}");
+                    await BlobService.RefreshBlobRoot(new BlobRefreshMessage { progress = "insert", root = $"exam", name = exam.school }, _serviceBus, _configuration, _azureRedis);
+                }
+                else
+                {
+                    exam.size = await _azureStorage.GetBlobContainerClient(exam.creatorId).GetBlobsSize($"exam/{exam.id}");
+                    await BlobService.RefreshBlobRoot(new BlobRefreshMessage { progress = "insert", root = $"exam", name = exam.creatorId }, _serviceBus, _configuration, _azureRedis);
+                }
+                int n = 0;
+                List<string> sheetIds = new List<string>();
+                foreach (PaperSimple simple in exam.papers)
+                {
+                    simple.blob = $"/exam/{exam.id}/paper/{exam.subjects[n].id}";
+                    n++;
+                    simple.sheet = null;
+                }
+                exam = await client.GetContainer(Constant.TEAMModelOS, "Common").CreateItemAsync(exam, new PartitionKey($"{exam.code}"));
+                await BIStats.SetTypeAddStats(client, _dingDing, exam.school, "Exam", 1);//BI统计增/减量
+            }
+            else if (response != null) //更新
+            {
+                using var json = await JsonDocument.ParseAsync(response.Content);
+                ExamInfo info = json.ToObject<ExamInfo>();
+                if (info.progress.Equals("going"))
+                {
+                    Result = "活动正在进行中,无法修改";
+                }
+                var messageBlob = new ServiceBusMessage();
+                if (exam.scope.Equals("school") && !string.IsNullOrWhiteSpace(exam.school))
+                {
+                    exam.size = await _azureStorage.GetBlobContainerClient(exam.school).GetBlobsSize($"exam/{exam.id}");
+                    await BlobService.RefreshBlobRoot(new BlobRefreshMessage { progress = "update", root = $"exam", name = exam.school }, _serviceBus, _configuration, _azureRedis);
+                }
+                else
+                {
+                    exam.size = await _azureStorage.GetBlobContainerClient(exam.creatorId).GetBlobsSize($"exam/{exam.id}");
+                    await BlobService.RefreshBlobRoot(new BlobRefreshMessage { progress = "update", root = $"exam", name = exam.creatorId }, _serviceBus, _configuration, _azureRedis);
+                }
+                exam.progress = info.progress;
+                int n = 0;
+                List<string> sheetIds = new List<string>();
+                foreach (PaperSimple simple in exam.papers)
+                {
+                    if (!string.IsNullOrEmpty(simple.subjectId))
+                    {
+                        simple.blob = $"/exam/{exam.id}/paper/{simple.subjectId}/{simple.id}";
+                    }
+                    else
+                    {
+                        simple.blob = $"/exam/{exam.id}/paper/{exam.subjects[n].id}";
+                        n++;
+                    }
+                    simple.sheet = null;
+                }
+                exam = await client.GetContainer(Constant.TEAMModelOS, "Common").ReplaceItemAsync(exam, exam.id, new PartitionKey($"{exam.code}"));
+            }
+            //Blob操作 ※取得試卷源(blob)、複製到評測紀錄下
+            ///試卷源字典
+            List<Dictionary<string, string>> sourcePaperInfo = new List<Dictionary<string, string>>();
+            foreach (PaperSimple paperInfo in jointExam.papers)
+            {
+                string paperBlobPath = (!paperInfo.blob.EndsWith("/")) ? paperInfo.blob + "/" : paperInfo.blob;
+                paperBlobPath = (paperInfo.blob.StartsWith("/")) ? paperBlobPath.Remove(0, 1) : paperBlobPath;
+                sourcePaperInfo.Add(new Dictionary<string, string>() { { "id", paperInfo.id }, { "blob", paperBlobPath }, { "itemcount", paperInfo.point.Count.ToString() } });
+            }
+            bool paperDataCopyErrFlg = false; //試卷資料拷貝錯誤Flag  true:拷貝錯誤
+            //Blob拷貝程序
+            int paperIndex = 0;
+            foreach (Dictionary<string, string> sourcePaperInfoDic in sourcePaperInfo)
+            {
+                //拷貝源:Container => jointExam.creatorId  Path:papers.blob
+                //拷貝對象:Container => exam.creatorId, Path:exam/{exam.id}/paper/{exam.subjects[paperIndex].id}/
+                string targetScope = exam.scope; //評測對象 school:校本班級  private:私人課程
+                var sourceBlobContainer = _azureStorage.GetBlobContainerClient(jointExam.creatorId); //統測活動來源一定是個人
+                var blobPrivateContainer = (targetScope.Equals("school")) ? _azureStorage.GetBlobContainerClient(exam.school) : _azureStorage.GetBlobContainerClient(exam.creatorId);
+                string sourceBlobPath = sourcePaperInfoDic["blob"];
+                string subjectId = exam.subjects[paperIndex].id;
+                string destBlobPath = $"exam/{exam.id}/paper/{subjectId}/"; //拷貝對象路徑 path:exam/{評測ID}/paper/{subjectID}/
+                Pageable<BlobItem> sourceBlobs = sourceBlobContainer.GetBlobs(prefix: sourceBlobPath);
+                if (sourceBlobs.Count() > 0)
+                {
+                    foreach (var blob in sourceBlobs)
+                    {
+                        var sourceFileBlob = sourceBlobContainer.GetBlobClient(blob.Name);
+                        if (sourceFileBlob.Exists())
+                        {
+                            var sourceFileUri = sourceBlobContainer.GetBlobClient(blob.Name).Uri;
+                            string fileName = blob.Name.Replace(sourceBlobPath, "");
+                            string destBlobFilePath = $"{destBlobPath}{fileName}";
+                            await blobPrivateContainer.GetBlobClient(destBlobFilePath).StartCopyFromUriAsync(sourceFileUri);
+                        }
+                        else
+                        {
+                            paperDataCopyErrFlg = true;
+                        }
+                    }
+                }
+                paperIndex++;
+            }
+
+            return Result;
+        }
+    }
+}

+ 92 - 56
TEAMModelOS/Controllers/Teacher/JointEventController.cs

@@ -22,6 +22,8 @@ using System.Linq;
 using OfficeOpenXml.FormulaParsing.LexicalAnalysis;
 using TEAMModelOS.Models.Dto;
 using TEAMModelOS.SDK.Models.Dtos;
+using DocumentFormat.OpenXml.Drawing;
+using DocumentFormat.OpenXml.Wordprocessing;
 
 namespace TEAMModelOS.Controllers.Common
 {
@@ -113,7 +115,7 @@ namespace TEAMModelOS.Controllers.Common
             }
         }
         /// <summary>
-        /// 新增修改統測活動 ※Login user為creator
+        /// 新增修改統測活動
         /// </summary>
         [ProducesDefaultResponseType]
         [Authorize(Roles = "IES")]
@@ -167,7 +169,7 @@ namespace TEAMModelOS.Controllers.Common
                 if(admin.Count > 0) jointEvent.admin = admin;
 
                 await client.GetContainer(Constant.TEAMModelOS, "Teacher").UpsertItemAsync<JointEvent>(jointEvent, new PartitionKey("JointEvent"));
-                return Ok(new { errCode = "", err = "" });
+                return Ok(new { errCode = "", err = "", jointEventId = $"{jointEvent.id}" });
             }
             catch (Exception e)
             {
@@ -281,19 +283,14 @@ namespace TEAMModelOS.Controllers.Common
         {
             try
             {
+                string errCode = string.Empty;
+                string err = string.Empty;
                 var client = _azureCosmos.GetCosmosClient();
                 long now = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds();
                 string jointEventId = (request.TryGetProperty("jointEventId", out JsonElement _jointEventId)) ? _jointEventId.ToString() : string.Empty;
-                string type = (request.TryGetProperty("type", out JsonElement _type)) ? _type.ToString() : string.Empty;
-                string examType = (request.TryGetProperty("examType", out JsonElement _examType)) ? _examType.ToString() : string.Empty;
-                bool examOverwrite = (request.TryGetProperty("examOverwrite", out JsonElement _examOverwrite)) ? _examOverwrite.GetBoolean() : false;
-                if (type.Equals("exam") && (!examType.Equals("regular") && !examType.Equals("Custom"))) return BadRequest();
-                string name = (request.TryGetProperty("name", out JsonElement _name)) ? _name.ToString() : string.Empty;
-                if (string.IsNullOrWhiteSpace(jointEventId) || string.IsNullOrWhiteSpace(type) || string.IsNullOrWhiteSpace(name)) return BadRequest();
-                string scheduleId = (request.TryGetProperty("scheduleId", out JsonElement _scheduleId)) ? _scheduleId.ToString() : string.Empty;
-                long startTime = (request.TryGetProperty("startTime", out JsonElement _startTime)) ? _startTime.GetInt64() : 0;
-                long endTime = (request.TryGetProperty("endTime", out JsonElement _endTime)) ? _endTime.GetInt64() : 0;
-                if(startTime.Equals(0) || endTime.Equals(0) || startTime > endTime) return BadRequest();
+                if(string.IsNullOrWhiteSpace(jointEventId)) return BadRequest();
+                List<JointEventSchedule> schedules = (request.TryGetProperty("schedules", out JsonElement _schedules)) ? _schedules.ToObject<List<JointEventSchedule>>() : new List<JointEventSchedule>();
+                if(schedules.Count.Equals(0)) return BadRequest();
 
                 //取得活動本體
                 JointEvent jointEvent = await client.GetContainer(Constant.TEAMModelOS, "Teacher").ReadItemAsync<JointEvent>(jointEventId, new PartitionKey("JointEvent"));
@@ -302,41 +299,56 @@ namespace TEAMModelOS.Controllers.Common
                     return Ok(new { errCode = "3", err = "Invalid jointEventId." });
                 }
                 //取得活動Schedule
-                if (!string.IsNullOrWhiteSpace(scheduleId))
+                foreach(JointEventSchedule schedule in schedules)
                 {
-                    JointEventSchedule jointEventSchedule = jointEvent.schedule.Where(s => s.id.Equals(scheduleId)).FirstOrDefault();
-                    if (jointEventSchedule != null)
+                    string mode = "add";
+                    JointEventSchedule jointEventSchedule = new JointEventSchedule();
+                    if (!string.IsNullOrWhiteSpace(schedule.id))
                     {
-                        jointEventSchedule.name = name;
-                        jointEventSchedule.type = type;
-                        jointEventSchedule.examType = (type.Equals("join")) ? null : examType;
-                        jointEventSchedule.examOverwrite = examOverwrite;
-                        jointEventSchedule.startTime = startTime;
-                        jointEventSchedule.endTime = endTime;
-                        jointEventSchedule.progress = (startTime <= now && now <= endTime) ? "going" : (now < startTime) ? "pending" : "finish";
+                        jointEventSchedule = jointEvent.schedule.Where(s => s.id.Equals(schedule.id)).FirstOrDefault();
+                        if (jointEventSchedule == null)
+                        {
+                            errCode = "5";
+                            err += $"Invalid scheduleId:{schedule.id}. \r\n";
+                            continue;
+                        }
+                        if (jointEventSchedule.progress.Equals("going"))
+                        {
+                            errCode = "10";
+                            err += $"JointSchedule (id:{schedule.id}) is going:, can't be changed. \r\n";
+                            continue;
+                        }
+                        mode = "upd";
                     }
                     else
                     {
-                        return Ok(new { errCode = "5", err = "Invalid scheduleId." });
+                        jointEventSchedule.id = Guid.NewGuid().ToString();
+                    }
+                    jointEventSchedule.name = schedule.name;
+                    jointEventSchedule.type = schedule.type;
+                    jointEventSchedule.examType = (schedule.type.Equals("join")) ? null : schedule.examType;
+                    jointEventSchedule.examOverwrite = schedule.examOverwrite;
+                    jointEventSchedule.startTime = schedule.startTime;
+                    jointEventSchedule.endTime = schedule.endTime;
+                    jointEventSchedule.progress = (schedule.startTime <= now && now <= schedule.endTime) ? "going" : (now < schedule.startTime) ? "pending" : "finish";
+                    jointEventSchedule.orderby = schedule.orderby;
+                    if (mode.Equals("add"))
+                    {
+                        jointEvent.schedule.Add(jointEventSchedule);
                     }
                 }
-                else
+                //排序
+                jointEvent.schedule = jointEvent.schedule.OrderBy(s => s.orderby).ToList();
+                int index = 0;
+                foreach (JointEventSchedule schedule in jointEvent.schedule)
                 {
-                    JointEventSchedule jointEventSchedule = new JointEventSchedule();
-                    jointEventSchedule.id = Guid.NewGuid().ToString();
-                    jointEventSchedule.name = name;
-                    jointEventSchedule.type = type;
-                    jointEventSchedule.examType = (type.Equals("join")) ? null : examType;
-                    jointEventSchedule.examOverwrite = examOverwrite;
-                    jointEventSchedule.startTime = startTime;
-                    jointEventSchedule.endTime = endTime;
-                    jointEventSchedule.progress = (startTime <= now && now <= endTime) ? "going" : (now < startTime) ? "pending" : "finish";
-                    jointEvent.schedule.Add(jointEventSchedule);
+                    schedule.orderby = index;
+                    index++;
                 }
 
                 //DB更新:JointEvent
                 await client.GetContainer(Constant.TEAMModelOS, "Teacher").ReplaceItemAsync<JointEvent>(jointEvent, jointEvent.id, new PartitionKey("JointEvent"));
-                return Ok(new { errCode = "", err = "" });
+                return Ok(new { errCode , err });
             }
             catch (Exception e)
             {
@@ -378,6 +390,15 @@ namespace TEAMModelOS.Controllers.Common
                 else
                 {
                     jointEvent.schedule.Remove(jointEventSchedule);
+                    //排序
+                    jointEvent.schedule = jointEvent.schedule.OrderBy(s => s.orderby).ToList();
+                    int index = 0;
+                    foreach (JointEventSchedule schedule in jointEvent.schedule)
+                    {
+                        schedule.orderby = index;
+                        index++;
+                    }
+
                     //DB更新:JointEvent
                     await client.GetContainer(Constant.TEAMModelOS, "Teacher").ReplaceItemAsync<JointEvent>(jointEvent, jointEvent.id, new PartitionKey("JointEvent"));
                     return Ok(new { errCode = "", err = "" });
@@ -505,7 +526,7 @@ namespace TEAMModelOS.Controllers.Common
             }
         }
         /// <summary>
-        /// 新建修改教師課程及組別
+        /// 刪除教師課程及組別
         /// </summary>
         [ProducesDefaultResponseType]
         [Authorize(Roles = "IES")]
@@ -613,7 +634,7 @@ namespace TEAMModelOS.Controllers.Common
 
                     }
                 }
-                List<JointExam> jointExamInfo = new List<JointExam>();
+                List<JointEvent> jointExamInfo = new List<JointEvent>();
                 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 }))
                 {
                     using var json = await JsonDocument.ParseAsync(item.Content);
@@ -621,7 +642,7 @@ namespace TEAMModelOS.Controllers.Common
                     {
                         foreach (var obj in json.RootElement.GetProperty("Documents").EnumerateArray())
                         {
-                            jointExamInfo.Add(obj.ToObject<JointExam>());
+                            jointExamInfo.Add(obj.ToObject<JointEvent>());
                         }
                     }
                     if (iscontinuation)
@@ -663,9 +684,9 @@ namespace TEAMModelOS.Controllers.Common
                 string jointScheduleId = (request.TryGetProperty("jointScheduleId", out JsonElement _jointScheduleId)) ? _jointScheduleId.ToString() : string.Empty;
                 string jointExamId = (request.TryGetProperty("jointExamId", out JsonElement _jointExamId)) ? _jointExamId.ToString() : string.Empty;
                 string creatorId = (request.TryGetProperty("creatorId", out JsonElement _creatorId)) ? _creatorId.ToString() : string.Empty;
-                string name = (request.TryGetProperty("creatorId", out JsonElement _name)) ? _name.ToString() : string.Empty;
+                string name = (request.TryGetProperty("name", out JsonElement _name)) ? _name.ToString() : string.Empty;
                 string source = (request.TryGetProperty("source", out JsonElement _source)) ? _source.ToString() : string.Empty;
-                PaperSimple paper = (request.TryGetProperty("paper", out JsonElement _paper)) ? _paper.ToObject<PaperSimple>() : null;
+                List<PaperSimple> papers = (request.TryGetProperty("papers", out JsonElement _papers)) ? _papers.ToObject<List<PaperSimple>>() : null;
                 List<JointEventGroupBase> stuLists = (request.TryGetProperty("stuLists", out JsonElement _stuLists)) ? _stuLists.ToObject<List<JointEventGroupBase>>() : null;
                 List<JointEventClassBase> classes = (request.TryGetProperty("classes", out JsonElement _classes)) ? _classes.ToObject<List<JointEventClassBase>>() : null;
 
@@ -703,22 +724,36 @@ namespace TEAMModelOS.Controllers.Common
                     return Ok(new { errCode = "3", err = "Invalid jointEventId." });
                 }
                 JointExam jointExam = new JointExam();
-                if(!string.IsNullOrWhiteSpace(jointExamId))
+                if(!string.IsNullOrWhiteSpace(jointExamId)) //更新
                 {
                     try
                     {
-                        jointExam = await client.GetContainer(Constant.TEAMModelOS, "Teacher").ReadItemAsync<JointExam>(jointExamId, new PartitionKey("JointExam"));
+                        jointExam = await client.GetContainer(Constant.TEAMModelOS, "Common").ReadItemAsync<JointExam>(jointExamId, new PartitionKey("JointExam"));
                     }
                     catch (Exception e)
                     {
                         return Ok(new { errCode = "6", err = "Invalid jointExamId." });
                     }
+                    if(jointExam.progress.Equals("going"))
+                    {
+                        return Ok(new { errCode = "9", err = "JointExam is going, can't be changed." });
+                    }
                     if (!string.IsNullOrWhiteSpace(name)) jointExam.name = name;
-
+                    if (string.IsNullOrWhiteSpace(source)) jointExam.source = source;
+                    if (jointEventSchedule.examType.Equals("custom")) //冠軍賽,要填classes或stuLists擇一必須
+                    {
+                        if (classes == null || stuLists == null)
+                        {
+                            return Ok(new { errCode = "8", err = "Invalid classes or stuLists." });
+                        }
+                        if (classes != null) jointExam.classes = classes;
+                        if (stuLists != null) jointExam.stuLists = stuLists;
+                    }
+                    if(papers != null) jointExam.papers = papers;
                 }
-                else
+                else //新建
                 {
-                    if(paper == null) return Ok(new { errCode = "7", err = "Invalid paper." });
+                    if(papers == null) return Ok(new { errCode = "7", err = "Invalid paper." });
                     if(string.IsNullOrWhiteSpace(creatorId)) return Ok(new { errCode = "8", err = "Invalid creatorId." });
                     if (string.IsNullOrWhiteSpace(name)) return Ok(new { errCode = "9", err = "Invalid name." });
                     if (string.IsNullOrWhiteSpace(source)) return Ok(new { errCode = "10", err = "Invalid source." });
@@ -728,28 +763,29 @@ namespace TEAMModelOS.Controllers.Common
                         {
                             return Ok(new { errCode = "8", err = "Invalid classes or stuLists." });
                         }
+                        if (classes != null) jointExam.classes = classes;
+                        if (stuLists != null) jointExam.stuLists = stuLists;
                     }
+                    jointExam.code = "JointExam";
                     jointExam.id = Guid.NewGuid().ToString();
                     jointExam.jointEventId = jointEventId;
                     jointExam.jointScheduleId = jointScheduleId;
                     jointExam.jointGroupId = jointGroupId;
                     jointExam.creatorId = creatorId;
                     jointExam.name = name;
-                    jointExam.examType = jointEventSchedule.examType;
-                    jointExam.examOverwrite = jointEventSchedule.examOverwrite;
-                    if(jointExam.examType.Equals("custom"))
-                    {
-                        if (classes != null) jointExam.classes = classes;
-                        if (stuLists != null) jointExam.stuLists = stuLists;
-                    }
                     jointExam.source = source;
-                    jointExam.papers.Add(paper);
-                    jointExam.startTime = jointEventSchedule.startTime;
-                    jointExam.endTime = jointEventSchedule.endTime;
-                    jointExam.progress = (jointExam.startTime > now ) ? "pending" : (jointExam.endTime > now) ? "finish" : "going";
+                    jointExam.papers = papers;
                     jointExam.createTime = now;
                 }
+                //共通
+                jointExam.examType = jointEventSchedule.examType;
+                jointExam.examOverwrite = jointEventSchedule.examOverwrite;
+                jointExam.startTime = jointEventSchedule.startTime;
+                jointExam.endTime = jointEventSchedule.endTime;
+                jointExam.progress = (jointExam.startTime > now) ? "pending" : (jointExam.endTime > now) ? "finish" : "going";
+                jointExam.updateTime = now;
 
+                await client.GetContainer(Constant.TEAMModelOS, "Common").UpsertItemAsync<JointExam>(jointExam, new PartitionKey("JointExam"));
                 return Ok(new { errCode = "", err = "" });
             }
             catch (Exception e)

+ 0 - 365
TEAMModelOS/Controllers/XTest/TestController.cs

@@ -3054,370 +3054,5 @@ namespace TEAMModelOS.Controllers
         }
         */
 
-        //統測活動資料架構生成測試
-        [HttpPost("add-joint-event")]
-        public async Task<IActionResult> AddJointEvent(JsonElement json)
-        {
-            var client = _azureCosmos.GetCosmosClient();
-
-            JointEvent jointEvent = new JointEvent();
-            jointEvent.id = "37db8a66-660c-43a5-b8ce-6686c070973e";
-            jointEvent.creatorId = "1595321354";
-            jointEvent.name = "統測活動(測試)";
-            jointEvent.pk = "JointEvent";
-            jointEvent.code = "JointEvent";
-            jointEvent.startTime = DateTimeOffset.UtcNow.AddDays(-4).ToUnixTimeMilliseconds();
-            jointEvent.endTime = DateTimeOffset.UtcNow.AddDays(7).ToUnixTimeMilliseconds();
-            jointEvent.progress = "going";
-            jointEvent.createTime = DateTimeOffset.UtcNow.AddDays(-4).ToUnixTimeMilliseconds();
-            //行程
-            ///行程1 報名
-            JointEventSchedule jointEventSchedule1 = new JointEventSchedule();
-            jointEventSchedule1.type = "join"; //報名
-            jointEventSchedule1.name = "報名";
-            jointEventSchedule1.startTime = DateTimeOffset.UtcNow.AddDays(-3).ToUnixTimeMilliseconds();
-            jointEventSchedule1.endTime = DateTimeOffset.UtcNow.AddDays(-1).ToUnixTimeMilliseconds();
-            jointEventSchedule1.progress = "finish";
-            jointEvent.schedule.Add(jointEventSchedule1);
-            ///行程2~4 評量
-            JointEventSchedule jointEventSchedule2 = new JointEventSchedule();
-            jointEventSchedule2.type = "exam"; //評量
-            jointEventSchedule2.name = "練習";
-            jointEventSchedule2.startTime = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds(); ;
-            jointEventSchedule2.endTime = DateTimeOffset.UtcNow.AddDays(2).ToUnixTimeMilliseconds();
-            jointEventSchedule2.progress = "going";
-            jointEvent.schedule.Add(jointEventSchedule2);
-            JointEventSchedule jointEventSchedule3 = new JointEventSchedule();
-            jointEventSchedule3.type = "exam"; //評量
-            jointEventSchedule3.name = "初賽";
-            jointEventSchedule3.startTime = DateTimeOffset.UtcNow.AddDays(3).ToUnixTimeMilliseconds();
-            jointEventSchedule3.endTime = DateTimeOffset.UtcNow.AddDays(5).ToUnixTimeMilliseconds();
-            jointEventSchedule3.progress = "pending";
-            jointEvent.schedule.Add(jointEventSchedule3);
-            JointEventSchedule jointEventSchedule4 = new JointEventSchedule();
-            jointEventSchedule4.type = "exam"; //評量
-            jointEventSchedule4.name = "決賽";
-            jointEventSchedule4.startTime = DateTimeOffset.UtcNow.AddDays(6).ToUnixTimeMilliseconds();
-            jointEventSchedule4.endTime = DateTimeOffset.UtcNow.AddDays(7).ToUnixTimeMilliseconds();
-            jointEventSchedule4.progress = "pending";
-            jointEvent.schedule.Add(jointEventSchedule4);
-
-            await client.GetContainer(Constant.TEAMModelOS, "Teacher").UpsertItemAsync<JointEvent>(jointEvent, new PartitionKey("JointEvent"));
-            return Ok();
-        }
-
-        //統測活動 教師課程生成測試
-        [HttpPost("add-joint-course")]
-        public async Task<IActionResult> AddJointCourse(JsonElement json)
-        {
-            var client = _azureCosmos.GetCosmosClient();
-
-            string jointEventId = "37db8a66-660c-43a5-b8ce-6686c070973e";
-            //第一位老師的課程、組別
-            JointEventGroupDb jointEventTGroup1 = new JointEventGroupDb();
-            jointEventTGroup1.id = "3f2c0d2e-c267-414a-a416-0029973e65f1";
-            jointEventTGroup1.jointEventId = jointEventId;
-            jointEventTGroup1.code = "JointCourse";
-            jointEventTGroup1.pk = "JointCourse";
-            jointEventTGroup1.creatorId = "1595321354";
-            jointEventTGroup1.creatorName = "Rocket Man";
-            JointEventGroupCourse jointEventTGroupCourse1 = new JointEventGroupCourse();
-            jointEventTGroupCourse1.courseId = "e1d817e5-71b3-bc50-52ac-fa828e5f38d6";
-            jointEventTGroupCourse1.courseName = "test";
-            jointEventTGroupCourse1.groupLists.Add(new JointEventGroupCourseGroup() { id = "4dd434a8-a94a-4373-9a05-1a1add533a71", name = "a2" });
-            jointEventTGroup1.courseLists.Add(jointEventTGroupCourse1);
-            await client.GetContainer(Constant.TEAMModelOS, "Teacher").UpsertItemAsync<JointEventGroupDb>(jointEventTGroup1, new PartitionKey("JointCourse"));
-
-            //第二位老師的課程、組別
-            JointEventGroupDb jointEventTGroup2 = new JointEventGroupDb();
-            jointEventTGroup2.id = "23f2fc0b-c881-43d2-8679-754bd965bb09";
-            jointEventTGroup2.jointEventId = jointEventId;
-            jointEventTGroup2.code = "JointCourse";
-            jointEventTGroup2.pk = "JointCourse";
-            jointEventTGroup2.creatorId = "1522758684";
-            jointEventTGroup2.creatorName = "DaTou  Fu";
-            JointEventGroupCourse jointEventTGroupCourse2 = new JointEventGroupCourse();
-            jointEventTGroupCourse2.courseId = "424eb44d-8904-4e34-bfaf-e138f8e369cb";
-            jointEventTGroupCourse2.courseName = "1522758684個人課程";
-            jointEventTGroupCourse2.groupLists.Add(new JointEventGroupCourseGroup() { id = "5bf63f35-f82f-432e-9b78-a4f38b5edb81", name = "暑期班" });
-            jointEventTGroup2.courseLists.Add(jointEventTGroupCourse2);
-            await client.GetContainer(Constant.TEAMModelOS, "Teacher").UpsertItemAsync<JointEventGroupDb>(jointEventTGroup2, new PartitionKey("JointCourse"));
-
-            return Ok();
-        }
-
-        //統測資料架構生成測試
-
-        [HttpPost("add-joint-exam")]
-        public async Task<IActionResult> AddJointExam(JsonElement json)
-        {
-            var client = _azureCosmos.GetCosmosClient();
-
-            JointExam jointExam = new JointExam();
-            jointExam.code = "JointExam";
-            jointExam.jointEventId = "37db8a66-660c-43a5-b8ce-6686c070973e";
-            jointExam.id = "daaa3965-db28-49ae-8074-c9d4865b62ad";
-            jointExam.creatorId = "1595321354";
-            jointExam.name = "統測測試(一)";
-            jointExam.scope = "private";
-            jointExam.source = "0";
-            //課程列表 ※課程列表可以從JointCourse拿,也可以前端挑選後加入
-            ////課程1: [1595321354]"courseId": "e1d817e5-71b3-bc50-52ac-fa828e5f38d6", "groupId": "4dd434a8-a94a-4373-9a05-1a1add533a71",
-            JointEventGroupBase jointExamGroup1 = new JointEventGroupBase();
-            jointExamGroup1.creatorId = "1595321354";
-            jointExamGroup1.creatorName = "Rocket Man";
-            JointEventGroupCourse jointEventTGroupCourse1 = new JointEventGroupCourse();
-            jointEventTGroupCourse1.courseId = "e1d817e5-71b3-bc50-52ac-fa828e5f38d6";
-            jointEventTGroupCourse1.courseName = "test";
-            jointEventTGroupCourse1.groupLists.Add(new JointEventGroupCourseGroup() { id = "4dd434a8-a94a-4373-9a05-1a1add533a71", name = "a2" } );
-            jointExamGroup1.courseLists.Add(jointEventTGroupCourse1);
-            jointExam.stuLists.Add(jointExamGroup1);
-            ////課程2: [1522758684]"courseId": "424eb44d-8904-4e34-bfaf-e138f8e369cb", "groupId": "5bf63f35-f82f-432e-9b78-a4f38b5edb81",
-            JointEventGroupBase jointExamGroup2 = new JointEventGroupBase();
-            jointExamGroup2.creatorId = "1522758684";
-            jointExamGroup2.creatorName = "DaTou  Fu";
-            JointEventGroupCourse jointEventTGroupCourse2 = new JointEventGroupCourse();
-            jointEventTGroupCourse2.courseId = "424eb44d-8904-4e34-bfaf-e138f8e369cb";
-            jointEventTGroupCourse2.courseName = "1522758684個人課程";
-            jointEventTGroupCourse2.groupLists.Add(new JointEventGroupCourseGroup() { id = "5bf63f35-f82f-432e-9b78-a4f38b5edb81", name = "暑期班" });
-            jointExamGroup2.courseLists.Add(jointEventTGroupCourse2);
-            jointExam.stuLists.Add(jointExamGroup2);
-            //試卷
-            ///STEP1 Blob拷貝 ※此步驟由前端做
-            ///拷貝目標:{TMID/SchoolId}/jointexam/{jointExam.id}/paper/{paperId}
-            string paperSourceBlobPath = "paper/語文理解";
-            string paperSourceBlobContainer = "tbslgb";
-            string paperSourceScope = "school";
-            var paperSourceBlobContainerClient = _azureStorage.GetBlobContainerClient(paperSourceBlobContainer);
-            string paperId = "1c6a1e85-4b34-b8af-00de-a379bc47a26c";
-            string paperTargetBlobPath = $"jointexam/{jointExam.id}/paper/{paperId}";
-            string paperTargetBlobContainer = jointExam.creatorId;
-            var paperTargetBlobContainerClient = _azureStorage.GetBlobContainerClient(paperTargetBlobContainer);
-            Pageable<BlobItem> sourceBlobs = paperSourceBlobContainerClient.GetBlobs(prefix: paperSourceBlobPath);
-            if (sourceBlobs.Count() > 0)
-            {
-                foreach (var blob in sourceBlobs)
-                {
-                    var sourceFileBlob = paperSourceBlobContainerClient.GetBlobClient(blob.Name);
-                    if (sourceFileBlob.Exists())
-                    {
-                        var sourceFileUri = paperSourceBlobContainerClient.GetBlobClient(blob.Name).Uri;
-                        string fileName = blob.Name.Replace(paperSourceBlobPath, "");
-                        string destBlobFilePath = $"{paperTargetBlobPath}{fileName}";
-                        await paperTargetBlobContainerClient.GetBlobClient(destBlobFilePath).StartCopyFromUriAsync(sourceFileUri);
-                    }
-                    else
-                    {
-                        bool paperDataCopyErrFlg = true;
-                    }
-                }
-            }
-            ///STEP2 document製作
-            PaperSimple paper = new PaperSimple() { id= paperId , code = "Paper-tbslgb", name = "語文理解", blob = $"/{paperTargetBlobPath}", scope = "school", multipleRule = 1, point = new List<double>() { 50, 50}, answers = new List<List<string>>() { new List<string>() { "D" }, new List<string>() { "D" } }, knowledge = new List<List<string>>(), type = new List<string>() { "single","single"}, field=new List<int>() { 2, 2 } };
-            jointExam.papers.Add(paper);
-            //時間
-            jointExam.createTime = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds();
-            jointExam.startTime = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds();
-            jointExam.endTime = DateTimeOffset.UtcNow.AddDays(2).ToUnixTimeMilliseconds();
-            jointExam.progress = "going";
-            await client.GetContainer(Constant.TEAMModelOS, "Common").UpsertItemAsync<JointExam>(jointExam, new PartitionKey("JointExam"));
-            return Ok();
-        }
-        
-
-        //評量統一生成測試
-        
-        [HttpPost("add-exam-byJoint")]
-        public async Task<IActionResult> AddExamByJointExam(JsonElement json)
-        {
-            if (!json.TryGetProperty("jointExamId", out JsonElement _jointExamId)) return BadRequest();
-            var client = _azureCosmos.GetCosmosClient();
-            string tmid = "1595321354";
-            JointExam jointExam = new JointExam();
-            await foreach (JointExam item in client.GetContainer(Constant.TEAMModelOS, "Common").GetItemQueryIteratorSql<JointExam>(
-                queryText: $"select * from c where c.id = '{_jointExamId}' and c.creatorId = '{tmid}'",
-                requestOptions: new QueryRequestOptions() { PartitionKey = new PartitionKey($"JointExam") }))
-            {
-                jointExam = item;
-            }
-            if(string.IsNullOrWhiteSpace(jointExam.id))
-            {
-                return BadRequest();
-            }
-
-            //評量資料生成 ExamInfo actExamInfo
-            List<ExamInfo> examList = new List<ExamInfo>();
-            if(jointExam.stuLists.Count > 0)
-            {
-                foreach (JointEventGroupBase jointExamGroup in jointExam.stuLists)
-                {
-                    string actExamCreatorId = jointExamGroup.creatorId;
-                    foreach(JointEventGroupCourse jointExamGroupCourse in jointExamGroup.courseLists)
-                    {
-                        string actExamCourseId = jointExamGroupCourse.courseId;
-                        string actExamCourseName = jointExamGroupCourse.courseName;
-                        //評量資料生成
-                        ExamInfo actExamInfo = new ExamInfo();
-                        actExamInfo.code = $"Exam-{jointExamGroup.creatorId}";
-                        actExamInfo.owner = "teacher";
-                        actExamInfo.scope = jointExam.scope;
-                        actExamInfo.creatorId = actExamCreatorId;
-                        actExamInfo.id = Guid.NewGuid().ToString();
-                        actExamInfo.source = jointExam.source;
-                        actExamInfo.name = jointExam.name;
-                        actExamInfo.jointExamId = jointExam.id;
-                        ///評量stuLists
-                        foreach (JointEventGroupCourseGroup actGroup in jointExamGroupCourse.groupLists)
-                        {
-                            actExamInfo.stuLists.Add(actGroup.id);
-                            List<string> targetRow = new List<string>() { actExamCourseId, actGroup.id };
-                            actExamInfo.targets.Add(System.Text.Json.JsonSerializer.SerializeToElement(targetRow));
-                            actExamInfo.subjects.Add(new ExamSubject() { id = actExamCourseId, name = actExamCourseName });
-                        }
-                        ///試卷 ※先用統購的試卷記入,生成評量(generateExam)時會做blob的路徑修正
-                        actExamInfo.papers = Newtonsoft.Json.JsonConvert.DeserializeObject<List<PaperSimple>>(Newtonsoft.Json.JsonConvert.SerializeObject(jointExam.papers));
-                        ///時間
-                        actExamInfo.year = DateTimeOffset.UtcNow.Year;
-                        actExamInfo.startTime = jointExam.startTime;
-                        actExamInfo.endTime = jointExam.endTime;
-
-                        examList.Add(actExamInfo);
-                    }
-                }
-            }
-
-            //生成評量
-            if(examList.Count > 0)
-            {
-                foreach(ExamInfo exam in examList)
-                {
-                    await generateExam(jointExam, exam);
-                }
-            }
-
-            return Ok();
-        }
-        
-        //生成評量(單)
-        
-        private async Task<string> generateExam(JointExam jointExam, ExamInfo exam)
-        {
-            var client = _azureCosmos.GetCosmosClient();
-            long now = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds();
-            string Result = string.Empty;
-            exam.createTime = now;
-            if (exam.startTime <= 0) exam.startTime = now;
-            List<(string pId, List<string> gid)> ps = new();
-            var group = exam.groupLists;
-            if (group.Count > 0)
-            {
-                foreach (var keys in group)
-                {
-                    foreach (KeyValuePair<string, List<string>> pp in keys)
-                    {
-                        ps.Add((pp.Key, pp.Value));
-                    }
-                }
-            }
-            List<string> classes = ExamService.getClasses(exam.classes, exam.stuLists);
-            (List<RMember> tchList, List<RGroupList> classLists) = await GroupListService.GetMemberByListids(_coreAPIHttpService, client, _dingDing, classes, exam.school, ps);
-            exam.stuCount = tchList.Count;
-            string mode = string.Empty;
-            ResponseMessage response = null;
-            if (string.IsNullOrEmpty(exam.id))
-            {
-                mode = "add";
-            }
-            else
-            {
-                response = await client.GetContainer("TEAMModelOS", "Common").ReadItemStreamAsync(exam.id, new PartitionKey($"{exam.code}"));
-                if (response.StatusCode==System.Net.HttpStatusCode.OK) mode = "upd";
-                else mode = "add";
-            }
-            //DB操作
-            if (mode.Equals("add")) //新建
-            {
-                if(string.IsNullOrWhiteSpace(exam.id)) exam.id = Guid.NewGuid().ToString();
-                exam.progress = (exam.startTime > now) ? "pending" : "going";
-                var messageBlob = new ServiceBusMessage();
-                exam.size = await _azureStorage.GetBlobContainerClient(exam.creatorId).GetBlobsSize($"exam/{exam.id}"); //統測只允許老師個人,Blob只指向creatorId
-                await BlobService.RefreshBlobRoot(new BlobRefreshMessage { progress = "insert", root = $"exam", name = exam.creatorId }, _serviceBus, _configuration, _azureRedis);
-                int n = 0;
-                List<string> sheetIds = new List<string>();
-                foreach (PaperSimple simple in exam.papers)
-                {
-                    simple.blob = $"/exam/{exam.id}/paper/{exam.subjects[n].id}";
-                    n++;
-                    simple.sheet = null;
-                }
-                exam = await client.GetContainer(Constant.TEAMModelOS, "Common").CreateItemAsync(exam, new PartitionKey($"{exam.code}"));
-                await BIStats.SetTypeAddStats(client, _dingDing, exam.school, "Exam", 1);//BI统计增/减量
-            }
-            else if(response != null) //更新
-            {
-                using var json = await JsonDocument.ParseAsync(response.Content);
-                ExamInfo info = json.ToObject<ExamInfo>();
-                if (info.progress.Equals("going"))
-                {
-                    Result = "活动正在进行中,无法修改";
-                }
-                var messageBlob = new ServiceBusMessage();
-                exam.size = await _azureStorage.GetBlobContainerClient(exam.creatorId).GetBlobsSize($"exam/{exam.id}");
-                await BlobService.RefreshBlobRoot(new BlobRefreshMessage { progress = "update", root = $"exam", name = exam.creatorId }, _serviceBus, _configuration, _azureRedis);
-                exam.progress = info.progress;
-                int n = 0;
-                foreach (PaperSimple simple in exam.papers)
-                {
-                    simple.blob = "/exam/" + exam.id + "/paper/" + exam.subjects[n].id;
-                    n++;
-                }
-                exam = await client.GetContainer(Constant.TEAMModelOS, "Common").ReplaceItemAsync(exam, exam.id, new PartitionKey($"{exam.code}"));
-            }
-            //Blob操作 ※取得試卷源(blob)、複製到評測紀錄下
-            ///試卷源字典
-            List<Dictionary<string, string>> sourcePaperInfo = new List<Dictionary<string, string>>();
-            foreach (PaperSimple paperInfo in jointExam.papers)
-            {
-                string paperBlobPath = (!paperInfo.blob.EndsWith("/")) ? paperInfo.blob + "/" : paperInfo.blob;
-                paperBlobPath = (paperInfo.blob.StartsWith("/")) ? paperBlobPath.Remove(0, 1) : paperBlobPath;
-                sourcePaperInfo.Add(new Dictionary<string, string>() { { "id", paperInfo.id }, { "blob", paperBlobPath }, { "itemcount", paperInfo.point.Count.ToString() } });
-            }
-            bool paperDataCopyErrFlg = false; //試卷資料拷貝錯誤Flag  true:拷貝錯誤
-            //Blob拷貝程序
-            int paperIndex = 0;
-            foreach (Dictionary<string, string> sourcePaperInfoDic in sourcePaperInfo)
-            {
-                //拷貝源:Container => jointExam.creatorId  Path:papers.blob
-                //拷貝對象:Container => exam.creatorId, Path:exam/{exam.id}/paper/{exam.subjects[paperIndex].id}/
-                string targetScope = exam.scope; //評測對象 school:校本班級  private:私人課程
-                var sourceBlobContainer = _azureStorage.GetBlobContainerClient(jointExam.creatorId);
-                var blobPrivateContainer = _azureStorage.GetBlobContainerClient(exam.creatorId);
-                string sourceBlobPath = sourcePaperInfoDic["blob"];
-                string subjectId = exam.subjects[paperIndex].id;
-                string destBlobPath = $"exam/{exam.id}/paper/{subjectId}/"; //拷貝對象路徑 path:exam/{評測ID}/paper/{subjectID}/
-                Pageable<BlobItem> sourceBlobs = sourceBlobContainer.GetBlobs(prefix: sourceBlobPath);
-                if (sourceBlobs.Count() > 0)
-                {
-                    foreach (var blob in sourceBlobs)
-                    {
-                        var sourceFileBlob = sourceBlobContainer.GetBlobClient(blob.Name);
-                        if (sourceFileBlob.Exists())
-                        {
-                            var sourceFileUri = sourceBlobContainer.GetBlobClient(blob.Name).Uri;
-                            string fileName = blob.Name.Replace(sourceBlobPath, "");
-                            string destBlobFilePath = $"{destBlobPath}{fileName}";
-                            await blobPrivateContainer.GetBlobClient(destBlobFilePath).StartCopyFromUriAsync(sourceFileUri);
-                        }
-                        else
-                        {
-                            paperDataCopyErrFlg = true;
-                        }
-                    }
-                }
-                paperIndex++;
-            }
-
-            return Result;
-        }
     }
 }