|
@@ -20,6 +20,8 @@ using static TEAMModelOS.SDK.CoreAPIHttpService;
|
|
|
using Microsoft.Azure.Cosmos;
|
|
|
using System.Collections;
|
|
|
using TEAMModelOS.SDK.Models.Cosmos.OpenEntity;
|
|
|
+using System.Globalization;
|
|
|
+using Microsoft.OData;
|
|
|
namespace TEAMModelOS.SDK.Models.Service
|
|
|
{
|
|
|
public static class SystemService
|
|
@@ -221,7 +223,42 @@ Hello {tmdname}, here is the summary report of the homework tasks you have poste
|
|
|
// static string en_lessonTitle = "Lesson attendance details:";
|
|
|
// static string en_groupTitle = "Personal course list change details:";
|
|
|
// #endregion
|
|
|
-
|
|
|
+ public static string weeklyReportCN = @"<!DOCTYPE html>
|
|
|
+<html>
|
|
|
+<head>
|
|
|
+ <title>HiTeach课堂数据周报</title>
|
|
|
+ <meta charset=""UTF-8"">
|
|
|
+ <style>
|
|
|
+ </style>
|
|
|
+
|
|
|
+</head>
|
|
|
+<body>
|
|
|
+ <div style=""margin: 0 0 20px 10px;"">
|
|
|
+ <p style=""font-size: 20px;"">
|
|
|
+ 尊敬的
|
|
|
+ <span id=""tmdName"">{tmdName}</span>
|
|
|
+ 教师您好,你于{year}第{Week}周({weekTime})创建了{lessonCount}节HiTeach5课堂教学活动,以下是报告总览信息,点击其中一行可查看报告详细信息
|
|
|
+ </p>
|
|
|
+ <div style=""margin-left: 10px;"">
|
|
|
+ <table border=""1"" cellpadding=""10"" cellspacing=""0"" border=""1"" cellpadding=""10"" border=""1"" id=""acourseData"">
|
|
|
+ <thead>
|
|
|
+ <tr>
|
|
|
+ <th>课程名称</th>
|
|
|
+ <th>科目</th>
|
|
|
+ <th>学校/个人</th>
|
|
|
+ <th>名单</th>
|
|
|
+ <th>课例数量</th>
|
|
|
+ <th>查看详情</th>
|
|
|
+ </tr>
|
|
|
+ </thead>
|
|
|
+ <tbody>
|
|
|
+ {tableData}
|
|
|
+ </tbody>
|
|
|
+ </table>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+</body>
|
|
|
+</html>";
|
|
|
/// <summary>
|
|
|
///
|
|
|
/// </summary>
|
|
@@ -898,6 +935,306 @@ Hello {tmdname}, here is the summary report of the homework tasks you have poste
|
|
|
}
|
|
|
}
|
|
|
|
|
|
+ public static async Task LessonWeekly( AzureRedisFactory _azureRedis, AzureCosmosFactory _azureCosmos, CoreAPIHttpService _coreAPIHttpService, DingDing _dingDing)
|
|
|
+ {
|
|
|
+ var now = DateTime.Now;
|
|
|
+ // 使用当前文化设置的日历
|
|
|
+ CultureInfo cultureInfo = CultureInfo.CurrentCulture;
|
|
|
+ Calendar calendar = cultureInfo.Calendar;
|
|
|
+ //表示如果一年的第一个星期至少有4天在同一年,则该星期被视为第一周,DayOfWeek.Monday和DayOfWeek.Sunday分别表示一周的第一天是星期一或星期日。
|
|
|
+
|
|
|
+ var week = calendar.GetWeekOfYear(now, CalendarWeekRule.FirstFourDayWeek, DayOfWeek.Monday);
|
|
|
+ string schoolKey = $"LessonWeekly:school:{now.Year}-{week}";
|
|
|
+ string privateKey = $"LessonWeekly:private:{now.Year}-{week}";
|
|
|
+
|
|
|
+ int currentDayOfWeek = (int)now.DayOfWeek;
|
|
|
+ // 计算周一的日期
|
|
|
+ DateTime startOfWeek = now.AddDays(1 - currentDayOfWeek);
|
|
|
+ // 计算周日的日期
|
|
|
+ DateTime endOfWeek = now.AddDays(7 - currentDayOfWeek);
|
|
|
+ // 获取当前年份
|
|
|
+ int currentYear = now.Year;
|
|
|
+ // 获取当前年份的第一天
|
|
|
+ DateTimeOffset firstDayOfYear = new DateTimeOffset(currentYear, 1, 1, 0, 0, 0,new TimeSpan(8,0,0));
|
|
|
+ // 获取当前年份的最后一天
|
|
|
+ DateTimeOffset lastDayOfYear = new DateTimeOffset(currentYear, 12, 31, 23, 59, 59, new TimeSpan(8, 0, 0));
|
|
|
+
|
|
|
+
|
|
|
+ var schoolValues = await _azureRedis.GetRedisClient(8).HashGetAllAsync(schoolKey);
|
|
|
+ var privateValues = await _azureRedis.GetRedisClient(8).HashGetAllAsync(privateKey);
|
|
|
+ List<KeyValuePair<string, List<LessonWeek>>> datas = new List<KeyValuePair<string, List<LessonWeek>>>();
|
|
|
+ HashSet<string> schoolId = new HashSet<string>();
|
|
|
+ if (schoolValues!=default && schoolValues.Length>0)
|
|
|
+ {
|
|
|
+ foreach (var value in schoolValues)
|
|
|
+ {
|
|
|
+ var tmdid = value.Name.ToString().Split(":")[0];
|
|
|
+ LessonWeek data = value.Value.ToString().ToObject<LessonWeek>();
|
|
|
+ if (!string.IsNullOrWhiteSpace(data.school))
|
|
|
+ {
|
|
|
+ schoolId.Add(data.school);
|
|
|
+ }
|
|
|
+ var tmdData = datas.Find(x => x.Key.Equals(tmdid));
|
|
|
+ if (tmdData.Key==null)
|
|
|
+ {
|
|
|
+ tmdData= new KeyValuePair<string, List<LessonWeek>>(tmdid, new List<LessonWeek> { data });
|
|
|
+ datas.Add(tmdData);
|
|
|
+ }
|
|
|
+ else
|
|
|
+ {
|
|
|
+ tmdData.Value.Add(data);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ if (privateValues!=default && privateValues.Length>0)
|
|
|
+ {
|
|
|
+ foreach (var value in privateValues)
|
|
|
+ {
|
|
|
+ var tmdid = value.Name.ToString().Split(":")[0];
|
|
|
+ LessonWeek data = value.Value.ToString().ToObject<LessonWeek>();
|
|
|
+ var tmdData = datas.Find(x => x.Key.Equals(tmdid));
|
|
|
+ if (tmdData.Key==null)
|
|
|
+ {
|
|
|
+ tmdData= new KeyValuePair<string, List<LessonWeek>>(tmdid, new List<LessonWeek> { data });
|
|
|
+ datas.Add(tmdData);
|
|
|
+ }
|
|
|
+ else
|
|
|
+ {
|
|
|
+ tmdData.Value.Add(data);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ if (datas.IsNotEmpty())
|
|
|
+ {
|
|
|
+
|
|
|
+ List<Teacher> teachers = new List<Teacher>();
|
|
|
+ string sql = $"select value c from c where c.id in ({string.Join(",", datas.Select(x => $"'{x.Key}'"))})";
|
|
|
+ var result = await _azureCosmos.GetCosmosClient().GetContainer(Constant.TEAMModelOS, Constant.Teacher).GetList<Teacher>(sql, "Base");
|
|
|
+ if (result.list.IsNotEmpty())
|
|
|
+ {
|
|
|
+ teachers.AddRange(result.list);
|
|
|
+ }
|
|
|
+ List<School> schools = new List<School>();
|
|
|
+ if (schoolId.IsNotEmpty())
|
|
|
+ {
|
|
|
+ var schoolIds= datas.SelectMany(x => x.Value).Where(x => !string.IsNullOrWhiteSpace(x.school)).Select(x => x.school).Distinct();
|
|
|
+ string schsql = $"select value c from c where c.id in ({string.Join(",", schoolIds.Select(x => $"'{x}'"))})";
|
|
|
+ var schresult = await _azureCosmos.GetCosmosClient().GetContainer(Constant.TEAMModelOS, Constant.School).GetList<School>(schsql, "Base");
|
|
|
+ if (schresult.list.IsNotEmpty())
|
|
|
+ {
|
|
|
+ schools.AddRange(schresult.list);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ //个人和学校名单
|
|
|
+ List<GroupListDto> groupLists = new List<GroupListDto>();
|
|
|
+ //个人和学校课程
|
|
|
+ List<CourseBase> courseBases = new List<CourseBase>();
|
|
|
+ foreach (var data in datas)
|
|
|
+ {
|
|
|
+ var teacher = teachers.Find(x => x.id.Equals(data.Key));
|
|
|
+ if (teacher!=null)
|
|
|
+ {
|
|
|
+ string lang = teacher.lang;
|
|
|
+ var tzt = now.GetGMTTime((int)teacher.timezone);
|
|
|
+ if (string.IsNullOrWhiteSpace(teacher.lang))
|
|
|
+ {
|
|
|
+ if (_coreAPIHttpService.options.location.Contains("China", StringComparison.OrdinalIgnoreCase))
|
|
|
+ {
|
|
|
+ lang= "zh-cn";
|
|
|
+ }
|
|
|
+ if (_coreAPIHttpService.options.location.Contains("Global", StringComparison.OrdinalIgnoreCase))
|
|
|
+ {
|
|
|
+ lang= "en-us";
|
|
|
+ }
|
|
|
+ }
|
|
|
+ List<LessonRecord> lessonRecords = new List<LessonRecord>();
|
|
|
+ #region
|
|
|
+ //课例
|
|
|
+ //{
|
|
|
+ // var pid = data.Value.FindAll(x => !string.IsNullOrWhiteSpace(x.scope) && x.scope.Equals("private")).Select(x => x.id);
|
|
|
+ // var sid = data.Value.FindAll(x => !string.IsNullOrWhiteSpace(x.scope) && x.scope.Equals("school"))
|
|
|
+ // .Select(x => new CodeValue { code=x.school, value=x.id }).GroupBy(x => x.code).Select(x => new { key = x.Key, list = x.ToList() });
|
|
|
+ // if (pid!=null && pid.Count()>0)
|
|
|
+ // {
|
|
|
+ // var pg = pid.ExceptBy(groupLists.Select(x => x.id), y => y);
|
|
|
+ // if (pg!=null && pg.Count()>0)
|
|
|
+ // {
|
|
|
+ // string csql = $"select value c from c where c.id in ({string.Join(",", pg.Select(x => $"'{x}'"))})";
|
|
|
+ // var lresult = await _azureCosmos.GetCosmosClient().GetContainer(Constant.TEAMModelOS, Constant.Teacher).GetList<LessonRecord>(csql, "LessonRecord");
|
|
|
+ // if (lresult.list.IsNotEmpty())
|
|
|
+ // {
|
|
|
+ // lessonRecords.AddRange(lresult.list);
|
|
|
+ // }
|
|
|
+ // }
|
|
|
+
|
|
|
+ // }
|
|
|
+ // if (sid!=null && sid.Count()>0)
|
|
|
+ // {
|
|
|
+ // foreach (var item in sid)
|
|
|
+ // {
|
|
|
+ // var sg = item.list.Select(x => x.value).ToList().ExceptBy(groupLists.Where(x => x.school.Equals(item.key)).Select(x => x.id), y => y);
|
|
|
+ // if (sg!=null && sg.Count()>0)
|
|
|
+ // {
|
|
|
+ // string csql = $"select value c from c where c.id in ({string.Join(",", sg.Select(x => $"'{x}'"))})";
|
|
|
+ // var lresult = await _azureCosmos.GetCosmosClient().GetContainer(Constant.TEAMModelOS, Constant.Teacher).GetList<LessonRecord>(csql, $"LessonRecord-{item.key}");
|
|
|
+ // if (lresult.list.IsNotEmpty())
|
|
|
+ // {
|
|
|
+ // lessonRecords.AddRange(lresult.list);
|
|
|
+ // }
|
|
|
+ // }
|
|
|
+ // }
|
|
|
+ // }
|
|
|
+ //}
|
|
|
+ //名单
|
|
|
+ {
|
|
|
+ var pgid = data.Value.FindAll(x => !string.IsNullOrWhiteSpace(x.scope) && x.scope.Equals("private")).Select(x => x.gid);
|
|
|
+ var sgid = data.Value.FindAll(x => !string.IsNullOrWhiteSpace(x.scope) && x.scope.Equals("school"))
|
|
|
+ .Select(x => new CodeValue { code=x.school, value=x.gid }).GroupBy(x => x.code).Select(x => new { key = x.Key, list = x.ToList() });
|
|
|
+ if (pgid!=null && pgid.Count()>0)
|
|
|
+ {
|
|
|
+ var pg = pgid.ExceptBy(groupLists.Select(x => x.id), y => y);
|
|
|
+ if (pg!=null && pg.Count()>0)
|
|
|
+ {
|
|
|
+ var grouplist = await GroupListService.GetGroupListByListids(_azureCosmos.GetCosmosClient(), _dingDing, pg.ToList(), null);
|
|
|
+ if (grouplist.IsNotEmpty())
|
|
|
+ {
|
|
|
+ groupLists.AddRange(grouplist);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ }
|
|
|
+ if (sgid!=null && sgid.Count()>0)
|
|
|
+ {
|
|
|
+ foreach (var item in sgid)
|
|
|
+ {
|
|
|
+ var sg = item.list.Select(x => x.value).ToList().ExceptBy(groupLists.Where(x => x.school.Equals(item.key)).Select(x => x.id), y => y);
|
|
|
+ if (sg!=null && sg.Count()>0)
|
|
|
+ {
|
|
|
+ var grouplist = await GroupListService.GetGroupListByListids(_azureCosmos.GetCosmosClient(), _dingDing, sg.ToList(), item.key);
|
|
|
+ if (grouplist.IsNotEmpty())
|
|
|
+ {
|
|
|
+ groupLists.AddRange(grouplist);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ //课程
|
|
|
+ {
|
|
|
+ var pcid = data.Value.FindAll(x => !string.IsNullOrWhiteSpace(x.scope) && x.scope.Equals("private") && !string.IsNullOrWhiteSpace(x.cid)).Select(x => x.cid);
|
|
|
+ var scid = data.Value.FindAll(x => !string.IsNullOrWhiteSpace(x.scope) && x.scope.Equals("school") && !string.IsNullOrWhiteSpace(x.cid))
|
|
|
+ .Select(x => new CodeValue { code=x.school, value=x.cid }).GroupBy(x => x.code).Select(x => new { key = x.Key, list = x.ToList() });
|
|
|
+ if (pcid!=null && pcid.Count()>0)
|
|
|
+ {
|
|
|
+ var pg = pcid.ExceptBy(courseBases.Select(x => x.id), y => y);
|
|
|
+ if (pg!=null && pg.Count()>0)
|
|
|
+ {
|
|
|
+ string csql = $"select value c from c where c.id in ({string.Join(",", pg.Select(x => $"'{x}'"))})";
|
|
|
+ var cresult = await _azureCosmos.GetCosmosClient().GetContainer(Constant.TEAMModelOS, Constant.Teacher).GetList<CourseBase>(csql, "CourseBase");
|
|
|
+ if (cresult.list.IsNotEmpty())
|
|
|
+ {
|
|
|
+ courseBases.AddRange(cresult.list);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ if (scid!=null && scid.Count()>0)
|
|
|
+ {
|
|
|
+ foreach (var item in scid)
|
|
|
+ {
|
|
|
+ var sg = item.list.Select(x => x.value).ToList().ExceptBy(courseBases.Where(x => x.school.Equals(item.key)).Select(x => x.id), y => y);
|
|
|
+ if (sg!=null && sg.Count()>0)
|
|
|
+ {
|
|
|
+ string csql = $"select value c from c where c.id in ({string.Join(",", sg.Select(x => $"'{x}'"))})";
|
|
|
+ var cresult = await _azureCosmos.GetCosmosClient().GetContainer(Constant.TEAMModelOS, Constant.School).GetList<CourseBase>(csql, $"CourseBase-{item.key}");
|
|
|
+ if (cresult.list.IsNotEmpty())
|
|
|
+ {
|
|
|
+ courseBases.AddRange(cresult.list);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ #endregion
|
|
|
+ StringBuilder sb= new StringBuilder();
|
|
|
+ var sdata = data.Value.Where(x => x.scope.Equals("school")).GroupBy(x => $"{x.scope}:{x.cid}:{x.gid}:{x.school}:{x.sid}").Select(x=>new { key=x.Key,list= x.ToList()});
|
|
|
+ var pdata = data.Value.Where(x => x.scope.Equals("private")).GroupBy(x => $"{x.scope}:{x.cid}:{x.gid}:").Select(x => new { key = x.Key, list = x.ToList() }); ;
|
|
|
+
|
|
|
+ foreach (var s in sdata)
|
|
|
+ {
|
|
|
+ Dictionary<string, string> @params = new ();
|
|
|
+ string[] ids = s.key.Split(":");
|
|
|
+ var course = courseBases.Find(x => x.id.Equals(ids[1]));
|
|
|
+ var group = groupLists.Find(x => x.id.Equals(ids[2]));
|
|
|
+ School school = null;
|
|
|
+ Subject subject = null;
|
|
|
+ Period period = null;
|
|
|
+ long stime = 0;
|
|
|
+ long etime = 0;
|
|
|
+ school = schools.Find(x => x.id.Equals(ids[3]));
|
|
|
+ if (school !=null)
|
|
|
+ {
|
|
|
+ period=school.period.FirstOrDefault();
|
|
|
+ subject = school.period.SelectMany(x => x.subjects).Where(x => x.id.Equals(ids[4])).FirstOrDefault();
|
|
|
+ var semester = SchoolService.GetSemester(period, DateTimeOffset.Now.ToUnixTimeMilliseconds());
|
|
|
+ stime = semester.currSemesterDate.ToUnixTimeMilliseconds();
|
|
|
+ etime = semester.nextSemester.ToUnixTimeMilliseconds();
|
|
|
+ }
|
|
|
+ if (stime==0)
|
|
|
+ {
|
|
|
+ stime= firstDayOfYear.ToUnixTimeMilliseconds();
|
|
|
+
|
|
|
+
|
|
|
+ }
|
|
|
+ if (etime==0)
|
|
|
+ {
|
|
|
+ etime= lastDayOfYear.ToUnixTimeMilliseconds();
|
|
|
+ }
|
|
|
+ @params.Add("tmdId", teacher.id);
|
|
|
+ @params.Add("school", school.id);
|
|
|
+ @params.Add("scope", "school");
|
|
|
+ @params.Add("courseId", course.id);
|
|
|
+ @params.Add("groupIds", group.id);
|
|
|
+ @params.Add("stime", $"{stime}");
|
|
|
+ @params.Add("etime", $"{etime}");
|
|
|
+ @params.Add("srvAdr", _coreAPIHttpService.options.location);
|
|
|
+ string page = lang.Equals("zh-cn") ? "reprotCN.html" : lang.Equals("zh-tw") ? "reprotTW.html" : "reprotUS.html";
|
|
|
+ string opt = lang.Equals("zh-cn") ? "查看" : lang.Equals("zh-tw") ? "檢視" : "View";
|
|
|
+ var host = _coreAPIHttpService.options.location.Contains("China", StringComparison.OrdinalIgnoreCase) ? "https://www.teammodel.cn" : "https://www.teammodel.net";
|
|
|
+ string paramStr = string.Join("&", @params.Select(x => $"{x.Key}={x.Value}"));
|
|
|
+ string url = $"{host}/{page}?{paramStr}";
|
|
|
+ sb.Append($"<tr><td>{course.name}</td><td>{subject.name}</td><td>{school.name}</td><td>{group.name}</td><td>{s.list.Count()}</td><td><a href='{url}'>{opt}</td>");
|
|
|
+ }
|
|
|
+ foreach (var s in pdata)
|
|
|
+ {
|
|
|
+ Dictionary<string, string> @params = new();
|
|
|
+ string[] ids = s.key.Split(":");
|
|
|
+ var course = courseBases.Find(x => x.id.Equals(ids[1]));
|
|
|
+ var group = groupLists.Find(x => x.id.Equals(ids[2]));
|
|
|
+ long stime = firstDayOfYear.ToUnixTimeMilliseconds();
|
|
|
+ long etime = lastDayOfYear.ToUnixTimeMilliseconds();
|
|
|
+ @params.Add("tmdId", teacher.id);
|
|
|
+ @params.Add("school", "");
|
|
|
+ @params.Add("scope", "school");
|
|
|
+ @params.Add("courseId", course.id);
|
|
|
+ @params.Add("groupIds", group.id);
|
|
|
+ @params.Add("stime", $"{stime}");
|
|
|
+ @params.Add("etime", $"{etime}");
|
|
|
+ @params.Add("srvAdr", _coreAPIHttpService.options.location);
|
|
|
+ string page = lang.Equals("zh-cn") ? "reprotCN.html" : lang.Equals("zh-tw") ? "reprotTW.html" : "reprotUS.html";
|
|
|
+ string opt = lang.Equals("zh-cn") ? "查看" : lang.Equals("zh-tw") ? "檢視" : "View";
|
|
|
+ string scopeStr = lang.Equals("zh-cn") ? "个人课程":lang.Equals("zh-tw") ? "個人課程" : "Personal Course";
|
|
|
+ var host = _coreAPIHttpService.options.location.Contains("China", StringComparison.OrdinalIgnoreCase) ? "https://www.teammodel.cn" : "https://www.teammodel.net";
|
|
|
+ string paramStr = string.Join("&", @params.Select(x => $"{x.Key}={x.Value}"));
|
|
|
+ string url = $"{host}/{page}?{paramStr}";
|
|
|
+ sb.Append($"<tr><td>{course.name}</td><td>--</td><td>{scopeStr}</td><td>{group.name}</td><td>{s.list.Count()}</td><td><a href='{url}'>{opt}</td>");
|
|
|
+ }
|
|
|
+ string html= weeklyReportCN.Replace("{tmdName}", teacher.name).Replace("{year}",$"{now.Year}").Replace("{Week}",$"{week}").Replace("{lessonCount}", $"{data.Value.Count()}").Replace("{tableData}",sb.ToString());
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
/// <summary>
|
|
|
/// 记录累计数据
|
|
|
/// </summary>
|
|
@@ -4150,6 +4487,41 @@ Hello {tmdname}, here is the summary report of the homework tasks you have poste
|
|
|
public List<SchoolStick> schoolSticks { get; set; } = new List<SchoolStick>();
|
|
|
public List<TchStick> tchSticks { get; set; } = new List<TchStick>();
|
|
|
}
|
|
|
+ public class LessonWeek
|
|
|
+ {
|
|
|
+
|
|
|
+ /// <summary>
|
|
|
+ /// 第几周
|
|
|
+ /// </summary>
|
|
|
+ public int week { get; set; }
|
|
|
+ /// <summary>
|
|
|
+ /// 课例id
|
|
|
+ /// </summary>
|
|
|
+ public string? id { get; set; }
|
|
|
+ /// <summary>
|
|
|
+ /// 课程id
|
|
|
+ /// </summary>
|
|
|
+ public string? cid { get; set; }
|
|
|
+ /// <summary>
|
|
|
+ /// 科目id
|
|
|
+ /// </summary>
|
|
|
+ public string? sid { get; set; }
|
|
|
+ /// <summary>
|
|
|
+ /// 学校
|
|
|
+ /// </summary>
|
|
|
+ public string? school { get; set; }
|
|
|
+ /// <summary>
|
|
|
+ /// 名单id
|
|
|
+ /// </summary>
|
|
|
+ public string? gid { get; set; }
|
|
|
+ /// <summary>
|
|
|
+ /// 学段id
|
|
|
+ /// </summary>
|
|
|
+ //public string? pid { get; set; }
|
|
|
+
|
|
|
+ public string? scope { get; set; }
|
|
|
+
|
|
|
+ }
|
|
|
public class Product
|
|
|
{
|
|
|
public string Name { get; set; }
|