ExamStudentService.cs 25 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620
  1. using Furion.DatabaseAccessor.Extensions;
  2. using NPOI.SS.UserModel;
  3. using System.Dynamic;
  4. using YBEE.EQM.Core;
  5. namespace YBEE.EQM.Application;
  6. /// <summary>
  7. /// 监测学生管理服务
  8. /// </summary>
  9. public class ExamStudentService : IExamStudentService, ITransient
  10. {
  11. private readonly IRepository<ExamStudent> _rep;
  12. private readonly IExamGradeService _examGradeService;
  13. private readonly ISysDictDataService _sysDictDataService;
  14. private readonly INceeCourseCombService _nceeCourseCombService;
  15. private readonly ISchoolClassService _schoolClassService;
  16. public ExamStudentService(IRepository<ExamStudent> rep, IExamGradeService examGradeService, ISysDictDataService sysDictDataService, INceeCourseCombService nceeCourseCombService, ISchoolClassService schoolClassService)
  17. {
  18. _rep = rep;
  19. _examGradeService = examGradeService;
  20. _sysDictDataService = sysDictDataService;
  21. _nceeCourseCombService = nceeCourseCombService;
  22. _schoolClassService = schoolClassService;
  23. }
  24. #region 批量导入
  25. /// <summary>
  26. /// 上传批量导入文件
  27. /// </summary>
  28. /// <param name="filePath"></param>
  29. /// <param name="examPlanId"></param>
  30. /// <returns></returns>
  31. public async Task<UploadExamDataOutput<UploadExamStudentOutput>> Upload(string filePath, int examPlanId)
  32. {
  33. UploadExamDataOutput<UploadExamStudentOutput> result = new();
  34. try
  35. {
  36. using FileStream fs = new(filePath, FileMode.Open, FileAccess.Read);
  37. IWorkbook workbook = ExcelUtil.GetWorkbook(filePath, fs);
  38. var sheet = workbook.GetSheetAt(0);
  39. var rows = sheet.GetRowEnumerator();
  40. #region 验证表结构
  41. // 少于2行验证
  42. if (sheet.LastRowNum < 2)
  43. {
  44. result.ErrorMessage.Add("第一行应为填写说明,第二行应为标题行,请勿修改模板结构。");
  45. return result;
  46. }
  47. // 跳过第一行
  48. rows.MoveNext();
  49. // 读取表头
  50. rows.MoveNext();
  51. IRow headerRow = (IRow)rows.Current;
  52. int index = 0;
  53. int CLASS_INDEX = index++;
  54. int NAME_INDEX = index++;
  55. int CERT_TYPE_INDEX = index++;
  56. int ID_NUM_INDEX = index++;
  57. int GENDER_INDEX = index++;
  58. int EXAM_NUM_INDEX = index++;
  59. int COURSE_COMB_INDEX = index++;
  60. int REMARK_INDEX = index++;
  61. int ROOM_INDEX = index++;
  62. int SEAT_INDEX = index;
  63. Dictionary<int, string> headers = new()
  64. {
  65. { CLASS_INDEX, "班级" },
  66. { NAME_INDEX, "姓名" },
  67. { CERT_TYPE_INDEX, "证件类型" },
  68. { ID_NUM_INDEX, "证件号码" },
  69. { GENDER_INDEX, "性别" },
  70. { EXAM_NUM_INDEX, "自编监测号" },
  71. { COURSE_COMB_INDEX, "选科组合" },
  72. { REMARK_INDEX, "备注" },
  73. { ROOM_INDEX, "考场号" },
  74. { SEAT_INDEX, "座位号" },
  75. };
  76. List<string> headerErrors = new();
  77. for (int i = 0; i <= index; i++)
  78. {
  79. if (headerRow.GetCell(i)?.ToString() != headers[i])
  80. {
  81. char letter = (char)('A' + i);
  82. headerErrors.Add(letter.ToString());
  83. }
  84. }
  85. if (headerErrors.Any())
  86. {
  87. string columnErrors = string.Join("、", headerErrors);
  88. result.ErrorMessage.Add($"第2行标题行{columnErrors}列名错误。从A列开始依次应为班级、姓名、证件类型、证件号码、性别、自编监测号、选科组合、备注、考场号和座位号。");
  89. return result;
  90. }
  91. result.StructureCorrect = true;
  92. #endregion
  93. #region 处理数据
  94. // 获取证件类型
  95. var cts = await _sysDictDataService.GetListByDictTypeId(304);
  96. var certificateTypes = cts.ToDictionary(x => x.Name, y => y.Value);
  97. // 获取选科组全
  98. var ccs = await _nceeCourseCombService.GetAllList();
  99. var courseCombs = ccs.ToDictionary(x => x.ShortName, y => y.Id);
  100. // 读取数据
  101. List<UploadExamStudentOutput> data = new();
  102. int rn = 0;
  103. while (rows.MoveNext())
  104. {
  105. IRow row = (IRow)rows.Current;
  106. string rv = row.GetCell(0)?.ToString().Trim() ?? "";
  107. rv += row.GetCell(1)?.ToString().Trim() ?? "";
  108. if (rv == "")
  109. {
  110. break;
  111. }
  112. UploadExamStudentOutput item = new() { RowNumber = ++rn };
  113. // 班级
  114. if (short.TryParse(row.GetCell(CLASS_INDEX)?.ToString(), out short classNumber))
  115. {
  116. item.ClassNumber = classNumber;
  117. if (item.ClassNumber < 1 || item.ClassNumber > 35)
  118. {
  119. item.ErrorMessage.Add($"{headers[CLASS_INDEX]}超限");
  120. }
  121. }
  122. else
  123. {
  124. item.ErrorMessage.Add(headers[CLASS_INDEX]);
  125. }
  126. // 姓名
  127. item.Name = StringUtil.ClearWhite(row.GetCell(NAME_INDEX)?.ToString() ?? "");
  128. if (item.Name == "" || item.Name.Length > 100)
  129. {
  130. item.ErrorMessage.Add(headers[NAME_INDEX]);
  131. }
  132. if (item.Name.Length > 100) { item.Name = item.Name[..100]; }
  133. // 证件类型
  134. item.CertificateTypeName = StringUtil.ClearWhite(row.GetCell(CERT_TYPE_INDEX)?.ToString() ?? "");
  135. if (!(item.CertificateTypeName != "" && certificateTypes.ContainsKey(item.CertificateTypeName)))
  136. {
  137. item.ErrorMessage.Add(headers[CERT_TYPE_INDEX]);
  138. }
  139. else
  140. {
  141. item.CertificateType = (CertificateType)certificateTypes[item.CertificateTypeName];
  142. }
  143. // 证件号码
  144. item.IdNumber = StringUtil.ClearWhite(row.GetCell(ID_NUM_INDEX)?.ToString() ?? "").ToUpper();
  145. if (item.CertificateType == CertificateType.ID_CARD)
  146. {
  147. var idNumberValidate = CertificateNumberValidator.ValidateIdCard(item.IdNumber);
  148. if (!idNumberValidate.Success)
  149. {
  150. item.ErrorMessage.Add($"{item.CertificateTypeName}{idNumberValidate.ErrorMessage}");
  151. }
  152. else
  153. {
  154. item.GenderName = idNumberValidate.Gender == Gender.MALE ? "男" : "女";
  155. item.Gender = idNumberValidate.Gender;
  156. }
  157. }
  158. else
  159. {
  160. // 性别
  161. item.GenderName = StringUtil.ClearWhite(row.GetCell(GENDER_INDEX)?.ToString() ?? "");
  162. if (item.CertificateType != CertificateType.ID_CARD)
  163. {
  164. item.Gender = item.GenderName == "男" ? Gender.MALE : item.GenderName == "女" ? Gender.FEMALE : Gender.UNKNOWN;
  165. }
  166. }
  167. if (item.IdNumber.Length > 50) { item.IdNumber = item.IdNumber[..50]; }
  168. // 自编监测号
  169. item.ExamNumber = StringUtil.ClearWhite(row.GetCell(EXAM_NUM_INDEX)?.ToString() ?? "");
  170. if (item.ExamNumber.Length > 50) { item.ExamNumber = item.ExamNumber[..50]; }
  171. // TODO 根据配置检测是否需要自编监测号
  172. // 选科组合
  173. item.NceeCourseCombName = StringUtil.ClearWhite(row.GetCell(COURSE_COMB_INDEX)?.ToString() ?? "");
  174. // TODO 高中需要处理选科
  175. if (item.NceeCourseCombName != "")
  176. {
  177. if (courseCombs.TryGetValue(item.NceeCourseCombName, out short value))
  178. {
  179. item.NceeCourseCombId = value;
  180. }
  181. else
  182. {
  183. item.ErrorMessage.Add(headers[COURSE_COMB_INDEX]);
  184. }
  185. }
  186. // 备注
  187. item.Remark = StringUtil.ClearWhite(row.GetCell(REMARK_INDEX)?.ToString() ?? "");
  188. if (item.Remark.Length > 200) { item.Remark = item.Remark[..200]; }
  189. // 考场号
  190. item.RoomNumber = StringUtil.ClearWhite(row.GetCell(ROOM_INDEX)?.ToString() ?? "");
  191. if (item.RoomNumber.Length > 20) { item.RoomNumber = item.RoomNumber[..20]; }
  192. // 座位号
  193. item.SeatNumber = StringUtil.ClearWhite(row.GetCell(SEAT_INDEX)?.ToString() ?? "");
  194. if (item.RoomNumber.Length > 20) { item.SeatNumber = item.SeatNumber[..20]; }
  195. // 行是否验证通过
  196. item.IsSuccess = item.ErrorMessage.Count == 0;
  197. data.Add(item);
  198. result.TotalRowCount++;
  199. if (!item.IsSuccess)
  200. {
  201. result.ErrorRowCount++;
  202. }
  203. }
  204. result.Rows = data;
  205. #endregion
  206. workbook.Close();
  207. fs.Close();
  208. }
  209. catch (Exception ex)
  210. {
  211. throw new Exception(ex.Message);
  212. }
  213. finally
  214. {
  215. File.Delete(filePath);
  216. }
  217. return result;
  218. }
  219. /// <summary>
  220. /// 批量导入监测学生
  221. /// </summary>
  222. /// <param name="input"></param>
  223. /// <returns></returns>
  224. public async Task Import(ImportExamStudentInput input)
  225. {
  226. var orgId = input.SysOrgId ?? CurrentSysUserInfo.SysOrgId;
  227. var examPlan = await _rep.Change<ExamPlan>().FirstOrDefaultAsync(t => t.Id == input.ExamPlanId);
  228. var examGrade = await _examGradeService.GetById(input.ExamGradeId);
  229. var classNumbers = input.Items.Select(t => t.ClassNumber).Distinct().ToList();
  230. var classDict = await _schoolClassService.GetImportSchoolClassList(new()
  231. {
  232. SysOrgId = orgId,
  233. SysOrgBranchId = input.SysOrgBranchId,
  234. ExamGrade = examGrade,
  235. ClassNumberList = classNumbers,
  236. });
  237. // 删除年级下所有数据
  238. await _rep.Where(t => t.ExamPlanId == input.ExamPlanId && t.SysOrgId == orgId && t.GradeId == input.GradeId && t.SysOrgBranchId == input.SysOrgBranchId).ExecuteDeleteAsync();
  239. List<ExamStudent> items = new();
  240. foreach (var ni in input.Items)
  241. {
  242. var item = ni.Adapt<ExamStudent>();
  243. item.ExamPlanId = input.ExamPlanId;
  244. item.SysOrgId = orgId;
  245. item.SysOrgBranchId = input.SysOrgBranchId;
  246. item.SchoolClassId = classDict[ni.ClassNumber];
  247. item.Name = StringUtil.ClearWhite(item.Name);
  248. item.IdNumber = StringUtil.ClearIdNumber(item.IdNumber);
  249. items.Add(item);
  250. }
  251. await _rep.InsertAsync(items);
  252. }
  253. #endregion
  254. #region 创建编辑
  255. /// <summary>
  256. /// 添加监测学生
  257. /// </summary>
  258. /// <param name="input"></param>
  259. /// <returns></returns>
  260. public async Task Add(AddExamStudentInput input)
  261. {
  262. var orgId = input.SysOrgId ?? CurrentSysUserInfo.SysOrgId;
  263. // 检测同一监测计划中同机构内是否有相同证件号码的学生
  264. var sameItems = await _rep.DetachedEntities.Where(t => t.ExamPlanId == input.ExamPlanId && t.SysOrgId == orgId && t.CertificateType == input.CertificateType && t.IdNumber == input.IdNumber).ProjectToType<ExamStudentOutput>().ToListAsync();
  265. if (sameItems.Any())
  266. {
  267. throw Oops.Oh(ErrorCode.E2003, string.Join("、", sameItems.Select(t => $"{t.ExamGrade.Grade.Name}{t.ClassNumber}班{t.Name}")), "证件号码");
  268. }
  269. var examGrade = await _examGradeService.GetById(input.ExamGradeId);
  270. var schoolClass = await _schoolClassService.GetSchoolClass(orgId, input.SysOrgBranchId, examGrade, input.ClassNumber);
  271. var item = input.Adapt<ExamStudent>();
  272. item.SysOrgId = orgId;
  273. item.SchoolClassId = schoolClass.Id;
  274. item.Name = StringUtil.ClearWhite(item.Name);
  275. item.IdNumber = StringUtil.ClearIdNumber(item.IdNumber);
  276. await item.InsertAsync();
  277. }
  278. /// <summary>
  279. /// 更新监测学生
  280. /// </summary>
  281. /// <param name="input"></param>
  282. /// <returns></returns>
  283. public async Task Update(UpdateExamStudentInput input)
  284. {
  285. var oitem = await _rep.DetachedEntities.FirstOrDefaultAsync(t => t.Id == input.Id) ?? throw Oops.Oh(ErrorCode.E2001);
  286. var examGrade = await _examGradeService.GetById(oitem.ExamGradeId);
  287. var schoolClass = await _schoolClassService.GetSchoolClass(oitem.SysOrgId, input.SysOrgBranchId, examGrade, input.ClassNumber);
  288. var item = input.Adapt<ExamStudent>();
  289. item.SchoolClassId = schoolClass.Id;
  290. item.Name = StringUtil.ClearWhite(item.Name);
  291. item.IdNumber = StringUtil.ClearIdNumber(item.IdNumber);
  292. await item.UpdateIncludeAsync(new[] {
  293. nameof(item.SchoolClassId),
  294. nameof(item.ClassNumber),
  295. nameof(item.Name),
  296. nameof(item.CertificateType),
  297. nameof(item.IdNumber),
  298. nameof(item.Gender),
  299. nameof(item.BirthDate),
  300. nameof(item.ExamNumber),
  301. nameof(item.StudentNumber),
  302. nameof(item.Remark),
  303. nameof(item.NceeCourseCombId),
  304. nameof(item.SysOrgBranchId),
  305. nameof(item.RoomNumber),
  306. nameof(item.SeatNumber),
  307. });
  308. }
  309. /// <summary>
  310. /// 删除监测学生
  311. /// </summary>
  312. /// <param name="input"></param>
  313. /// <returns></returns>
  314. public async Task Del(BaseId<long> input)
  315. {
  316. var item = await _rep.FirstOrDefaultAsync(t => t.Id == input.Id) ?? throw Oops.Oh(ErrorCode.E2001);
  317. await item.DeleteAsync();
  318. }
  319. /// <summary>
  320. /// 清空监测学生
  321. /// </summary>
  322. /// <param name="input"></param>
  323. /// <returns></returns>
  324. public async Task Clear(ClearExamStudentInput input)
  325. {
  326. var orgId = CurrentSysUserInfo.SysOrgId;
  327. await _rep.Where(t => t.ExamPlanId == input.ExamPlanId && t.SysOrgId == orgId).ExecuteDeleteAsync();
  328. }
  329. #endregion
  330. #region 查询统计
  331. /// <summary>
  332. /// 分页查询监测学生列表
  333. /// </summary>
  334. /// <param name="input"></param>
  335. /// <returns></returns>
  336. public async Task<PageResult<ExamStudentOutput>> QueryPageList(ExamStudentPageInput input)
  337. {
  338. var query = GetQueryBase(input);
  339. query = query.Where(t => t.ExamPlanId == input.ExamPlanId && t.SysOrgId == CurrentSysUserInfo.SysOrgId);
  340. var ret = await query.OrderBy(t => t.GradeId).ThenBy(t => t.ClassNumber).ThenBy(t => t.Id).ProjectToType<ExamStudentOutput>().ToADPagedListAsync(input.PageIndex, input.PageSize);
  341. return ret;
  342. }
  343. /// <summary>
  344. /// 获取机构班级上报人数统计列表
  345. /// </summary>
  346. /// <param name="examPlanId"></param>
  347. /// <returns></returns>
  348. public async Task<ExamStudentGradeClassStudentCountOutput> GetOrgGradeClassStudentCount(int examPlanId)
  349. {
  350. var orgId = CurrentSysUserInfo.SysOrgId;
  351. var items = await _rep.DetachedEntities.Where(t => t.ExamPlanId == examPlanId && t.SysOrgId == orgId)
  352. .GroupBy(t => new { t.GradeId, t.ClassNumber })
  353. .Select(t => new
  354. {
  355. t.Key.GradeId,
  356. t.Key.ClassNumber,
  357. t.FirstOrDefault().Grade,
  358. Count = t.Count(),
  359. })
  360. .ToListAsync();
  361. var cols = items.Select(t => t.ClassNumber).Distinct().ToList();
  362. var retItems = items.ToPivotList(c => c.ClassNumber, r => r.GradeId, d => d.Any() ? d.Sum(x => x.Count) : 0);
  363. foreach (var item in retItems)
  364. {
  365. item.Grade = items.FirstOrDefault(t => t.GradeId == item.GradeId).Grade;
  366. item.GradeTotal = items.Where(t => t.GradeId == item.GradeId).Sum(x => x.Count);
  367. }
  368. int total = retItems.Sum(x => x.GradeTotal);
  369. IDictionary<string, object> totalItem = new ExpandoObject();
  370. totalItem.Add("GradeId", 9999);
  371. totalItem.Add("Grade", new { Id = 9999, Name = "合计" });
  372. totalItem.Add("GradeTotal", total);
  373. retItems.Add(totalItem);
  374. return new()
  375. {
  376. ClassNumberList = cols,
  377. Items = retItems,
  378. Total = total,
  379. };
  380. }
  381. /// <summary>
  382. /// 分页查询班级学生人数
  383. /// </summary>
  384. /// <param name="input"></param>
  385. /// <returns></returns>
  386. public async Task<PageResult<ExamStudentCountItem>> QueryStudentCountPageList(ExamStudentCountPageInput input)
  387. {
  388. List<string> whereCause = new();
  389. if (!string.IsNullOrEmpty(input.SysOrgName?.Trim()))
  390. {
  391. whereCause.Add("T1.sys_org_full_name LIKE '%@sysOrgName%'");
  392. }
  393. if (!string.IsNullOrEmpty(input.SysOrgCode?.Trim()))
  394. {
  395. whereCause.Add("T1.sys_org_code LIKE '%@sysOrgCode%'");
  396. }
  397. if (input.UrbanRuralType.HasValue)
  398. {
  399. whereCause.Add("T1.urban_rural_type = @urbanRuralType");
  400. }
  401. if (input.GradeId.HasValue)
  402. {
  403. whereCause.Add("T1.grade_id = @gradeId");
  404. }
  405. if (input.ClassNumber.HasValue)
  406. {
  407. whereCause.Add("T1.class_number = @classNumber");
  408. }
  409. if (input.ClassCountMin.HasValue)
  410. {
  411. whereCause.Add("T2.class_count >= @classCountMin");
  412. }
  413. if (input.ClassCountMax.HasValue)
  414. {
  415. whereCause.Add("T2.class_count <= @classCountMax");
  416. }
  417. if (input.StudentCountMin.HasValue)
  418. {
  419. whereCause.Add("T1.student_count >= @studentCountMin");
  420. }
  421. if (input.StudentCountMax.HasValue)
  422. {
  423. whereCause.Add("T1.student_count <= @studentCountMax");
  424. }
  425. if (input.ExcludeSchoolClassIds != null && input.ExcludeSchoolClassIds.Any())
  426. {
  427. string excludeScids = string.Join(", ", input.ExcludeSchoolClassIds);
  428. whereCause.Add($"T1.school_class_id NOT IN ({excludeScids})");
  429. }
  430. if (input.IncludeSchoolClassIds != null)
  431. {
  432. if (input.IncludeSchoolClassIds.Count == 0)
  433. {
  434. whereCause.Add($"T1.school_class_id IN (0)");
  435. }
  436. else
  437. {
  438. string includeScids = string.Join(", ", input.IncludeSchoolClassIds);
  439. whereCause.Add($"T1.school_class_id IN ({includeScids})");
  440. }
  441. }
  442. string whereSql = $"WHERE T1.exam_plan_id = @examPlanId";
  443. if (whereCause.Count > 0)
  444. {
  445. whereSql = $"{whereSql} AND {string.Join(" AND ", whereCause)}";
  446. }
  447. var p = new
  448. {
  449. input.PageSize,
  450. PageOffset = (input.PageIndex - 1) * input.PageSize,
  451. input.SysOrgName,
  452. input.ExamPlanId,
  453. input.GradeId,
  454. input.ClassCountMin,
  455. input.ClassCountMax,
  456. input.ClassNumber,
  457. input.StudentCountMin,
  458. input.StudentCountMax,
  459. input.UrbanRuralType,
  460. };
  461. var totalCount = await _rep.SqlScalarAsync<int>($@"
  462. SELECT COUNT(T1.school_class_id) AS total_count
  463. FROM
  464. (
  465. SELECT
  466. T1.exam_plan_id,
  467. T1.sys_org_id,
  468. T4.full_name AS sys_org_full_name,
  469. T4.`name` AS sys_org_name,
  470. T4.`code` AS sys_org_code,
  471. T4.urban_rural_type,
  472. T1.school_class_id,
  473. T1.grade_id,
  474. T5.`name` AS grade_name,
  475. T1.class_number,
  476. COUNT(1) AS student_count
  477. FROM exam_student AS T1
  478. JOIN school_class AS T3 ON T1.school_class_id = T3.id
  479. JOIN sys_org AS T4 ON T1.sys_org_id = T4.id
  480. JOIN base_grade AS T5 ON T1.grade_id = T5.id
  481. WHERE T1.exam_plan_id = @examPlanId
  482. GROUP BY T1.exam_plan_id, T1.sys_org_id, T4.full_name, T4.`name`, T4.`code`, T4.urban_rural_type, T1.school_class_id, T1.grade_id, T5.`name`, T1.class_number
  483. ) AS T1
  484. JOIN
  485. (
  486. SELECT T1.sys_org_id, T1.grade_id, COUNT(DISTINCT T1.school_class_id) AS class_count
  487. FROM exam_student AS T1
  488. WHERE T1.exam_plan_id = @examPlanId
  489. GROUP BY T1.sys_org_id, T1.grade_id
  490. ) AS T2 ON T1.sys_org_id = T2.sys_org_id AND T1.grade_id = T2.grade_id
  491. {whereSql}
  492. ", p);
  493. var items = await _rep.SqlQueriesAsync<ExamStudentCountItem>($@"
  494. SELECT T1.*, T2.class_count, T3.grade_count
  495. FROM
  496. (
  497. SELECT
  498. T1.exam_plan_id,
  499. T1.sys_org_id,
  500. T4.full_name AS sys_org_full_name,
  501. T4.`name` AS sys_org_name,
  502. T4.`code` AS sys_org_code,
  503. T4.urban_rural_type,
  504. T1.sys_org_branch_id,
  505. T6.`name` AS sys_org_branch_name,
  506. T1.school_class_id,
  507. T3.grade_begin_year,
  508. T1.grade_id,
  509. T5.`name` AS grade_name,
  510. T5.grade_number,
  511. T1.class_number,
  512. COUNT(1) AS student_count
  513. FROM exam_student AS T1
  514. JOIN school_class AS T3 ON T1.school_class_id = T3.id
  515. JOIN sys_org AS T4 ON T1.sys_org_id = T4.id
  516. JOIN base_grade AS T5 ON T1.grade_id = T5.id
  517. LEFT JOIN sys_org AS T6 ON T1.sys_org_branch_id = T6.id
  518. WHERE T1.exam_plan_id = @examPlanId
  519. GROUP BY T1.exam_plan_id, T1.sys_org_id, T4.full_name, T4.`name`, T4.`code`, T4.urban_rural_type, T1.sys_org_branch_id, T6.`name`, T1.school_class_id, T1.grade_id, T3.grade_begin_year, T5.`name`, T5.grade_number, T1.class_number
  520. ) AS T1
  521. JOIN
  522. (
  523. SELECT T1.sys_org_id, T1.grade_id, COUNT(DISTINCT T1.school_class_id) AS class_count
  524. FROM exam_student AS T1
  525. WHERE T1.exam_plan_id = @examPlanId
  526. GROUP BY T1.sys_org_id, T1.grade_id
  527. ) AS T2 ON T1.sys_org_id = T2.sys_org_id AND T1.grade_id = T2.grade_id
  528. JOIN
  529. (
  530. SELECT T1.sys_org_id, COUNT(DISTINCT T1.grade_id) AS grade_count
  531. FROM exam_student AS T1
  532. WHERE T1.exam_plan_id = @examPlanId
  533. GROUP BY T1.sys_org_id
  534. ) AS T3 ON T1.sys_org_id = T3.sys_org_id
  535. {whereSql}
  536. ORDER BY T1.sys_org_code, T1.grade_id, T1.class_number
  537. LIMIT @pageSize OFFSET @pageOffset;
  538. ", p);
  539. PageResult<ExamStudentCountItem> ret = new()
  540. {
  541. PageIndex = input.PageIndex,
  542. PageSize = input.PageSize,
  543. TotalCount = totalCount,
  544. Items = items
  545. };
  546. return ret;
  547. }
  548. #endregion
  549. #region 私有方法
  550. /// <summary>
  551. /// 构建查询
  552. /// </summary>
  553. /// <param name="input"></param>
  554. /// <returns></returns>
  555. private IQueryable<ExamStudent> GetQueryBase(ExamStudentPageInput input)
  556. {
  557. var name = !string.IsNullOrEmpty(input.Name?.Trim());
  558. var idNumber = !string.IsNullOrEmpty(input.IdNumber?.Trim());
  559. var examNumber = !string.IsNullOrEmpty(input.ExamNumber?.Trim());
  560. var studentNumber = !string.IsNullOrEmpty(input.StudentNumber?.Trim());
  561. var roomNumber = !string.IsNullOrEmpty(input.RoomNumber?.Trim());
  562. var seatNumber = !string.IsNullOrEmpty(input.SeatNumber?.Trim());
  563. var query = _rep.DetachedEntities.Where((name, u => EF.Functions.Like(u.Name, $"%{input.Name.Trim()}%")))
  564. .Where((idNumber, u => EF.Functions.Like(u.IdNumber, $"%{input.IdNumber.Trim()}%")))
  565. .Where((examNumber, u => EF.Functions.Like(u.ExamNumber, $"%{input.ExamNumber.Trim()}%")))
  566. .Where((studentNumber, u => EF.Functions.Like(u.StudentNumber, $"%{input.StudentNumber.Trim()}%")))
  567. .Where((roomNumber, u => EF.Functions.Like(u.RoomNumber, $"%{input.RoomNumber.Trim()}%")))
  568. .Where((seatNumber, u => EF.Functions.Like(u.SeatNumber, $"%{input.SeatNumber.Trim()}%")))
  569. .Where(input.CertificateType.HasValue, t => t.CertificateType == input.CertificateType)
  570. .Where(input.Gender.HasValue, t => t.Gender == input.Gender)
  571. .Where(input.GradeId.HasValue, t => t.GradeId == input.GradeId)
  572. .Where(input.NceeCourseCombId.HasValue, t => t.NceeCourseCombId == input.NceeCourseCombId)
  573. .Where(input.ClassNumber.HasValue, t => t.ClassNumber == input.ClassNumber)
  574. .Where(input.SysOrgBranchId.HasValue, t => t.SchoolClass.SysOrgBranchId == input.SysOrgBranchId);
  575. return query;
  576. }
  577. #endregion
  578. }