ExamStudentService.cs 25 KB

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