ExamSampleService.cs 63 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424
  1. using Furion.DatabaseAccessor.Extensions;
  2. using Furion.JsonSerialization;
  3. using NPOI.SS.UserModel;
  4. using NPOI.SS.Util;
  5. using NPOI.XSSF.UserModel;
  6. using YBEE.EQM.Core;
  7. namespace YBEE.EQM.Application;
  8. /// <summary>
  9. /// 监测抽样方案管理服务
  10. /// </summary>
  11. public class ExamSampleService : IExamSampleService, ITransient
  12. {
  13. private readonly IRepository<ExamSample> _rep;
  14. private readonly IExportExcelService _exportExcelService;
  15. private readonly ISysDictDataService _sysDictDataService;
  16. public ExamSampleService(IRepository<ExamSample> rep, IExportExcelService exportExcelService, ISysDictDataService sysDictDataService)
  17. {
  18. _rep = rep;
  19. _exportExcelService = exportExcelService;
  20. _sysDictDataService = sysDictDataService;
  21. }
  22. #region 方案管理
  23. /// <summary>
  24. /// 添加监测抽样方案
  25. /// </summary>
  26. /// <param name="input"></param>
  27. /// <returns></returns>
  28. public async Task Add(AddExamSampleInput input)
  29. {
  30. var examPlan = await _rep.Change<ExamPlan>().DetachedEntities.FirstOrDefaultAsync(t => t.Id == input.ExamPlanId) ?? throw Oops.Oh(ErrorCode.E2001, "监测计划");
  31. var maxSeq = await _rep.DetachedEntities.Where(t => t.ExamPlanId == input.ExamPlanId).MaxAsync(t => (short?)t.Sequence);
  32. var item = input.Adapt<ExamSample>();
  33. item.Sequence = (short)(maxSeq.HasValue ? (maxSeq + 1) : 1);
  34. string sampleName = $"学生抽样方案{ConvertUtil.ConvertToChinese(item.Sequence)}";
  35. item.FullName = $"{examPlan.FullName}{sampleName}";
  36. item.Name = $"{examPlan.Name}{sampleName}";
  37. item.ShortName = $"{examPlan.ShortName}{sampleName}";
  38. await item.InsertAsync();
  39. }
  40. /// <summary>
  41. /// 更新监测抽样方案
  42. /// </summary>
  43. /// <param name="input"></param>
  44. /// <returns></returns>
  45. public async Task Update(UpdateExamSampleInput input)
  46. {
  47. if (!await _rep.AnyAsync(t => t.Id == input.Id))
  48. {
  49. throw Oops.Oh(ErrorCode.E2001);
  50. }
  51. var item = input.Adapt<ExamSample>();
  52. await item.UpdateIncludeAsync(new[] {
  53. nameof(item.Name),
  54. nameof(item.FullName),
  55. nameof(item.ShortName),
  56. nameof(item.Remark),
  57. nameof(item.Config)
  58. });
  59. }
  60. /// <summary>
  61. /// 复制抽样方案信息
  62. /// </summary>
  63. /// <param name="input"></param>
  64. /// <returns></returns>
  65. public async Task Duplicate(BaseId input)
  66. {
  67. var item = await _rep.DetachedEntities.FirstOrDefaultAsync(t => t.Id == input.Id) ?? throw Oops.Oh(ErrorCode.E2001);
  68. var examPlan = await _rep.Change<ExamPlan>().DetachedEntities.FirstOrDefaultAsync(t => t.Id == item.ExamPlanId) ?? throw Oops.Oh(ErrorCode.E2001, "监测计划");
  69. var maxSeq = await _rep.DetachedEntities.Where(t => t.ExamPlanId == item.ExamPlanId).MaxAsync(t => (short?)t.Sequence);
  70. short sequence = (short)(maxSeq.HasValue ? (maxSeq + 1) : 1);
  71. string sampleName = $"学生抽样方案{ConvertUtil.ConvertToChinese(sequence)}";
  72. ExamSample newItem = new()
  73. {
  74. ExamPlanId = item.ExamPlanId,
  75. Sequence = sequence,
  76. FullName = $"{examPlan.FullName}{sampleName}",
  77. Name = $"{examPlan.Name}{sampleName}",
  78. ShortName = $"{examPlan.ShortName}{sampleName}",
  79. Status = ExamSampleStatus.INITIAL,
  80. Config = item.Config,
  81. Remark = item.Remark,
  82. };
  83. await newItem.InsertAsync();
  84. }
  85. /// <summary>
  86. /// 删除监测抽样方案
  87. /// </summary>
  88. /// <param name="input"></param>
  89. /// <returns></returns>
  90. public async Task Del(BaseId input)
  91. {
  92. var item = await _rep.FirstOrDefaultAsync(t => t.Id == input.Id) ?? throw Oops.Oh(ErrorCode.E2001);
  93. var examPlan = await _rep.Change<ExamPlan>().DetachedEntities.FirstOrDefaultAsync(t => t.Id == item.ExamPlanId) ?? throw Oops.Oh(ErrorCode.E2001, "监测计划");
  94. if (examPlan.IsFixedExamSample || item.Status == ExamSampleStatus.RUNNING || item.IsSelected == true)
  95. {
  96. throw Oops.Oh(ErrorCode.E3001);
  97. }
  98. // 批量删除已抽测学生
  99. await _rep.Change<ExamSampleStudent>().Where(t => t.ExamSampleId == item.Id).ExecuteDeleteAsync();
  100. // 删除抽样方案
  101. await item.DeleteNowAsync();
  102. }
  103. /// <summary>
  104. /// 保存全抽班级ID列表
  105. /// </summary>
  106. /// <param name="input"></param>
  107. /// <returns></returns>
  108. public async Task SaveExamSampleAllClasses(SaveExamSampleAllClasses input)
  109. {
  110. var item = await _rep.FirstOrDefaultAsync(t => t.Id == input.Id) ?? throw Oops.Oh(ErrorCode.E2001);
  111. var config = JSON.Deserialize<ExamSampleConfig>(item.Config);
  112. config.SampleAllSchoolClassIds = input.ClassIds;
  113. item.Config = JSON.Serialize(config);
  114. await item.UpdateIncludeAsync(new[] { nameof(item.Config) });
  115. }
  116. /// <summary>
  117. /// 切换全抽班级
  118. /// </summary>
  119. /// <param name="input"></param>
  120. /// <returns></returns>
  121. public async Task SwitchExamSampleAllClass(SwitchExamSampleAllClassInput input)
  122. {
  123. var item = await _rep.FirstOrDefaultAsync(t => t.Id == input.Id) ?? throw Oops.Oh(ErrorCode.E2001);
  124. var config = JSON.Deserialize<ExamSampleConfig>(item.Config);
  125. if (input.IsAdd && !config.SampleAllSchoolClassIds.Any(t => t == input.SchoolClassId))
  126. {
  127. config.SampleAllSchoolClassIds.Add(input.SchoolClassId);
  128. }
  129. else if (!input.IsAdd && config.SampleAllSchoolClassIds.Any(t => t == input.SchoolClassId))
  130. {
  131. config.SampleAllSchoolClassIds.Remove(input.SchoolClassId);
  132. }
  133. item.Config = JSON.Serialize(config);
  134. await item.UpdateIncludeAsync(new[] { nameof(item.Config) });
  135. }
  136. /// <summary>
  137. /// 选定方案
  138. /// </summary>
  139. /// <param name="input"></param>
  140. /// <returns></returns>
  141. public async Task SelectSample(BaseId input)
  142. {
  143. var item = await _rep.FirstOrDefaultAsync(t => t.Id == input.Id) ?? throw Oops.Oh(ErrorCode.E2001);
  144. var examPlan = await _rep.Change<ExamPlan>().FirstOrDefaultAsync(t => t.Id == item.ExamPlanId) ?? throw Oops.Oh(ErrorCode.E2001, "监测计划");
  145. item.IsSelected = true;
  146. item.SelectedTime = DateTime.Now;
  147. item.SelectedSysUserId = CurrentSysUserInfo.SysUserId;
  148. examPlan.IsFixedExamSample = true;
  149. await item.UpdateAsync();
  150. await examPlan.UpdateAsync();
  151. }
  152. /// <summary>
  153. /// 执行抽样
  154. /// </summary>
  155. /// <param name="input">抽样方案ID</param>
  156. /// <returns></returns>
  157. /// <exception cref="Exception"></exception>
  158. public async Task ExecuteSample(BaseId input)
  159. {
  160. // 抽样方案
  161. var item = await _rep.FirstOrDefaultAsync(t => t.Id == input.Id) ?? throw Oops.Oh(ErrorCode.E2001, "抽样方案");
  162. // 监测计划
  163. var plan = await _rep.Change<ExamPlan>().DetachedEntities
  164. .ProjectToType<ExamPlanOutput>()
  165. .FirstOrDefaultAsync(t => t.Id == item.ExamPlanId) ?? throw Oops.Oh(ErrorCode.E2001, "监测计划");
  166. // 监测年级字典
  167. var examGradeDict = plan.ExamGrades.ToDictionary(t => t.GradeId);
  168. var examSample = item.Adapt<ExamSampleOutput>();
  169. try
  170. {
  171. var sampleItem = item.Adapt<ExamSampleOutput>();
  172. // 更新状态为【生成中】
  173. item.Status = ExamSampleStatus.RUNNING;
  174. await item.UpdateIncludeNowAsync(new[] { nameof(item.Status) });
  175. // 获取所有学生信息
  176. var stus = await GetStudentScoreList(sampleItem.Config, item.ExamPlanId, plan.ExamGrades.Select(t => t.GradeId).Distinct().ToList());
  177. // 获取抽样学生信息
  178. var sampleStus = GetSampleList(sampleItem.Config, examGradeDict, stus);
  179. // 获取最终抽样学生列表
  180. var finalSampleStus = GetFinalSampleList(examSample, examGradeDict, sampleStus);
  181. // 删除已存在数据
  182. string deleteSql = $"DELETE FROM exam_sample_student WHERE exam_sample_id = {input.Id}";
  183. await _rep.SqlNonQueryAsync(deleteSql);
  184. #region 批量写入
  185. int si = 0;
  186. int stuCount = finalSampleStus.Count;
  187. List<string> insertValues = new();
  188. foreach (var stu in finalSampleStus)
  189. {
  190. si++;
  191. var iss = stu.IsSpecialStudent ? 1 : 0;
  192. string valueSql = $"({stu.ExamSampleId}, {stu.ExamStudentId}, '{stu.ExamNumber}', {stu.Sequence}, {(short)stu.ExamSampleType}, {iss}, {stu.PreTotalScore})";
  193. insertValues.Add(valueSql);
  194. // 一次写入2000行
  195. if (si % 2000 == 0 || si == stuCount)
  196. {
  197. string insertSql = $"INSERT INTO exam_sample_student(exam_sample_id, exam_student_id, exam_number, sequence, exam_sample_type, is_special_student, pre_total_score) VALUES {string.Join(", ", insertValues)}";
  198. await _rep.SqlNonQueryAsync(insertSql);
  199. insertValues.Clear();
  200. }
  201. }
  202. #endregion
  203. // 更新状态为【已生成】
  204. item.Status = ExamSampleStatus.SUCCESSFUL;
  205. await item.UpdateIncludeNowAsync(new[] { nameof(item.Status) });
  206. }
  207. catch (Exception ex)
  208. {
  209. // 更新状态为【已失败】
  210. item.Status = ExamSampleStatus.FAILED;
  211. await item.UpdateIncludeNowAsync(new[] { nameof(item.Status) });
  212. throw new Exception(ex.Message);
  213. }
  214. }
  215. #endregion
  216. #region 导出抽样数据
  217. /// <summary>
  218. /// 导出抽样方案存档文件
  219. /// </summary>
  220. /// <param name="id"></param>
  221. /// <param name="hideIdNumber"></param>
  222. /// <returns></returns>
  223. public async Task<(string fileName, byte[] fileBytes)> ExportToArchived(int id, bool hideIdNumber = false)
  224. {
  225. // 抽样方案
  226. var examSample = await _rep.DetachedEntities.FirstOrDefaultAsync(t => t.Id == id) ?? throw Oops.Oh(ErrorCode.E2001, "抽样方案");
  227. // 监测计划
  228. var plan = await _rep.Change<ExamPlan>().DetachedEntities
  229. .ProjectToType<ExamPlanOutput>()
  230. .FirstOrDefaultAsync(t => t.Id == examSample.ExamPlanId) ?? throw Oops.Oh(ErrorCode.E2001, "监测计划");
  231. // 监测年级字典
  232. var examGradeDict = plan.ExamGrades.ToDictionary(t => t.GradeId);
  233. // 获取证件类型
  234. var cts = await _sysDictDataService.GetListByDictTypeId(304);
  235. var certificateTypes = cts.ToDictionary(x => (Core.CertificateType)x.Value, y => y.Name);
  236. // 临时存放目录
  237. string fileRoot = Path.Combine(FileUtil.GetTempFileRoot(), $"{Guid.NewGuid()}");
  238. Directory.CreateDirectory(fileRoot);
  239. // 抽样文件目录
  240. string sampleFileRoot = Path.Combine(fileRoot, examSample.FullName);
  241. string selectStusSql = @$"
  242. SELECT
  243. T2.sys_org_id,
  244. T4.full_name AS sys_org_full_name,
  245. T4.`name` AS sys_org_name,
  246. T4.`code` AS sys_org_code,
  247. T2.sys_org_branch_id,
  248. T5.`name` AS sys_org_branch_name,
  249. T2.grade_id,
  250. T3.grade_number,
  251. T3.`name` AS grade_name,
  252. T2.school_class_id,
  253. T2.class_number,
  254. T1.exam_student_id,
  255. T2.`name` AS exam_student_name,
  256. T2.certificate_type,
  257. T2.id_number,
  258. T1.exam_number,
  259. T1.exam_sample_type
  260. FROM exam_sample_student AS T1
  261. JOIN exam_student AS T2 ON T1.exam_student_id = T2.id
  262. JOIN base_grade AS T3 ON T2.grade_id = T3.id
  263. JOIN sys_org AS T4 ON T2.sys_org_id = T4.id
  264. LEFT JOIN sys_org AS T5 ON T2.sys_org_branch_id = T5.id
  265. WHERE T1.exam_sample_id = {id}
  266. ";
  267. // 所有学生
  268. var stus = await _rep.SqlQueriesAsync<ExamSampleStudentExportDto>(selectStusSql);
  269. // 按年级生成考生文件
  270. var ws = stus.Select(t => new { t.SysOrgId, t.SysOrgFullName, t.SysOrgCode, t.SysOrgBranchId, t.SysOrgBranchName, t.GradeId })
  271. .OrderBy(t => t.SysOrgId).ThenBy(t => t.SysOrgBranchId).ThenBy(t => t.GradeId).Distinct().ToList();
  272. foreach (var w in ws)
  273. {
  274. // 监测年级
  275. var eg = examGradeDict[w.GradeId];
  276. // 学校目录
  277. string orgDir = Path.Combine(sampleFileRoot, $"{w.SysOrgCode}-{w.SysOrgFullName}");
  278. if (!Directory.Exists(orgDir))
  279. {
  280. Directory.CreateDirectory(orgDir);
  281. }
  282. // 校区名称
  283. string branchName = "";
  284. if (w.SysOrgBranchId != null)
  285. {
  286. branchName = $"@{w.SysOrgBranchName}";
  287. }
  288. #region 导出抽中学生
  289. string sampleFileName = $"{w.SysOrgCode}-{w.SysOrgFullName}{branchName}-{eg.Grade.Name}-{ExamSampleType.DISTRICT.GetDescription()}-学生检录表";
  290. // 抽中学生列表
  291. var sampleStus = stus.Where(t => t.SysOrgId == w.SysOrgId && t.SysOrgBranchId == w.SysOrgBranchId && t.GradeId == w.GradeId && t.ExamSampleType == ExamSampleType.DISTRICT)
  292. .OrderBy(t => t.ExamNumber?[^4..]).ToList();
  293. // 自编号按导入顺序
  294. if (eg.IsRequiredSelfExamNumber)
  295. {
  296. sampleStus = sampleStus.OrderBy(t => t.ExamStudentId).ToList();
  297. }
  298. // 定义EXCEL列
  299. List<ExportExcelColDto<ExamSampleStudentExportDto>> cols = new()
  300. {
  301. new() { Name = "年级号", Width = 8, GetCellValue = (r) => r.GradeNumber },
  302. new() { Name = "班级号", Width = 8, GetCellValue = (r) => r.ClassNumber },
  303. new() { Name = "监测号", Width = 14, GetCellValue = (r) => r.ExamNumber },
  304. new() { Name = "姓名", Width = 20, GetCellValue = (r) => r.ExamStudentName },
  305. };
  306. if (w.SysOrgBranchId != null)
  307. {
  308. cols.Insert(0, new() { Name = "校区", Width = 10, GetCellValue = (r) => r.SysOrgBranchName });
  309. }
  310. if (!hideIdNumber)
  311. {
  312. cols.Add(new() { Name = "证件类型", Width = 16, GetCellValue = (r) => certificateTypes[r.CertificateType] });
  313. cols.Add(new() { Name = "证件号码", Width = 20, GetCellValue = (r) => r.IdNumber });
  314. }
  315. cols.Add(new() { Name = "名单类型", Width = 10, GetCellValue = (r) => r.ExamSampleType.GetDescription() });
  316. // 导出EXCEL文件
  317. var ret = _exportExcelService.ExportExcel(new ExportExcelDto<ExamSampleStudentExportDto>()
  318. {
  319. Title = sampleFileName,
  320. Columns = cols,
  321. Items = sampleStus,
  322. IncludeExportTime = false,
  323. });
  324. // 写入文件
  325. await File.WriteAllBytesAsync(Path.Combine(orgDir, $"{sampleFileName}.xlsx"), ret);
  326. #endregion
  327. #region 导出未抽中学生
  328. string noSampleFileName = $"{w.SysOrgCode}-{w.SysOrgFullName}{branchName}-{eg.Grade.Name}-{ExamSampleType.SCHOOL_EXAM.GetDescription()}-学生检录表";
  329. // 抽中学生列表
  330. var noSampleStus = stus.Where(t => t.SysOrgId == w.SysOrgId && t.SysOrgBranchId == w.SysOrgBranchId && t.GradeId == w.GradeId && t.ExamSampleType == ExamSampleType.SCHOOL_EXAM)
  331. .OrderBy(t => t.ExamNumber?[^4..]).ToList();
  332. // 自编号按导入顺序
  333. if (eg.IsRequiredSelfExamNumber)
  334. {
  335. noSampleStus = noSampleStus.OrderBy(t => t.ExamStudentId).ToList();
  336. }
  337. // 定义EXCEL列
  338. List<ExportExcelColDto<ExamSampleStudentExportDto>> noCols = new()
  339. {
  340. new() { Name = "年级号", Width = 8, GetCellValue = (r) => r.GradeNumber },
  341. new() { Name = "班级号", Width = 8, GetCellValue = (r) => r.ClassNumber },
  342. new() { Name = "考号", Width = 14, GetCellValue = (r) => r.ExamNumber },
  343. new() { Name = "姓名", Width = 20, GetCellValue = (r) => r.ExamStudentName },
  344. };
  345. if (w.SysOrgBranchId != null)
  346. {
  347. noCols.Insert(0, new() { Name = "校区", Width = 10, GetCellValue = (r) => r.SysOrgBranchName });
  348. }
  349. if (!hideIdNumber)
  350. {
  351. noCols.Add(new() { Name = "证件类型", Width = 16, GetCellValue = (r) => certificateTypes[r.CertificateType] });
  352. noCols.Add(new() { Name = "证件号码", Width = 20, GetCellValue = (r) => r.IdNumber });
  353. }
  354. noCols.Add(new() { Name = "名单类型", Width = 10, GetCellValue = (r) => r.ExamSampleType.GetDescription() });
  355. // 导出EXCEL文件
  356. var noRet = _exportExcelService.ExportExcel(new ExportExcelDto<ExamSampleStudentExportDto>()
  357. {
  358. Title = noSampleFileName,
  359. Columns = noCols,
  360. Items = noSampleStus,
  361. IncludeExportTime = false,
  362. });
  363. // 写入文件
  364. await File.WriteAllBytesAsync(Path.Combine(orgDir, $"{noSampleFileName}.xlsx"), noRet);
  365. #endregion
  366. }
  367. // 统计表文件
  368. var countFile = await ExportSampleCount(id);
  369. await File.WriteAllBytesAsync(Path.Combine(sampleFileRoot, countFile.fileName), countFile.fileBytes);
  370. string outFileName = $"{examSample.FullName}.zip";
  371. string outFilePath = Path.Combine(fileRoot, outFileName);
  372. ICSharpCode.SharpZipLib.Zip.FastZip zip = new();
  373. zip.CreateZip(outFilePath, sampleFileRoot, true, string.Empty);
  374. var retBytes = await File.ReadAllBytesAsync(outFilePath);
  375. Directory.Delete(fileRoot, true);
  376. return (outFileName, retBytes);
  377. }
  378. /// <summary>
  379. /// 导出给印刷厂和网阅机构文件
  380. /// </summary>
  381. /// <param name="id"></param>
  382. /// <returns></returns>
  383. public async Task<(string fileName, byte[] fileBytes)> ExportToPrintshop(int id)
  384. {
  385. var item = await _rep.DetachedEntities.FirstOrDefaultAsync(t => t.Id == id && t.Status == ExamSampleStatus.SUCCESSFUL && t.IsSelected == true);
  386. return item == null ? throw Oops.Oh(ErrorCode.E2006) : await ExportToArchived(id, true);
  387. }
  388. /// <summary>
  389. /// 导出给学校
  390. /// </summary>
  391. /// <param name="id"></param>
  392. /// <returns></returns>
  393. public async Task<(string fileName, byte[] fileBytes)> ExportToOrg(int id)
  394. {
  395. // 抽样方案
  396. var examSample = await _rep.DetachedEntities.FirstOrDefaultAsync(t => t.Id == id && t.ExamPlan.IsFixedExamSample == true && t.Status == ExamSampleStatus.SUCCESSFUL && t.IsSelected == true) ?? throw Oops.Oh(ErrorCode.E2001, "抽样方案");
  397. // 监测计划
  398. var plan = await _rep.Change<ExamPlan>().DetachedEntities
  399. .ProjectToType<ExamPlanOutput>()
  400. .FirstOrDefaultAsync(t => t.Id == examSample.ExamPlanId) ?? throw Oops.Oh(ErrorCode.E2001, "监测计划");
  401. // 必须发布了的才能下载
  402. var pb = await _rep.Change<ExamDataPublish>().DetachedEntities.AnyAsync(t => t.ExamPlanId == plan.Id && t.Type == DataPublishType.STUDENT_SAMPLE_LIST && t.Status == PublishStatus.PUBLISHED);
  403. if (!pb)
  404. {
  405. throw Oops.Oh(ErrorCode.E2006);
  406. }
  407. // 监测年级字典
  408. var examGradeDict = plan.ExamGrades.ToDictionary(t => t.GradeId);
  409. // 获取证件类型
  410. var cts = await _sysDictDataService.GetListByDictTypeId(304);
  411. var certificateTypes = cts.ToDictionary(x => (Core.CertificateType)x.Value, y => y.Name);
  412. // 临时存放目录
  413. string fileRoot = Path.Combine(FileUtil.GetTempFileRoot(), $"{Guid.NewGuid()}");
  414. Directory.CreateDirectory(fileRoot);
  415. string selectStusSql = @$"
  416. SELECT
  417. T2.sys_org_id,
  418. T4.full_name AS sys_org_full_name,
  419. T4.`name` AS sys_org_name,
  420. T4.`code` AS sys_org_code,
  421. T2.sys_org_branch_id,
  422. T5.`name` AS sys_org_branch_name,
  423. T2.grade_id,
  424. T3.grade_number,
  425. T3.`name` AS grade_name,
  426. T2.school_class_id,
  427. T2.class_number,
  428. T1.exam_student_id,
  429. T2.`name` AS exam_student_name,
  430. T2.certificate_type,
  431. T2.id_number,
  432. T1.exam_number,
  433. T1.exam_sample_type
  434. FROM exam_sample_student AS T1
  435. JOIN exam_student AS T2 ON T1.exam_student_id = T2.id
  436. JOIN base_grade AS T3 ON T2.grade_id = T3.id
  437. JOIN sys_org AS T4 ON T2.sys_org_id = T4.id
  438. LEFT JOIN sys_org AS T5 ON T2.sys_org_branch_id = T5.id
  439. WHERE T1.exam_sample_id = {id} AND T2.sys_org_id = {CurrentSysUserInfo.SysOrgId}
  440. ";
  441. // 所有学生
  442. var stus = await _rep.SqlQueriesAsync<ExamSampleStudentExportDto>(selectStusSql);
  443. // 按年级生成考生文件
  444. var ws = stus.Select(t => new { t.SysOrgId, t.SysOrgFullName, t.SysOrgCode, t.SysOrgBranchId, t.SysOrgBranchName, t.GradeId })
  445. .OrderBy(t => t.SysOrgId).ThenBy(t => t.SysOrgBranchId).ThenBy(t => t.GradeId).Distinct().ToList();
  446. string fileName = "";
  447. foreach (var w in ws)
  448. {
  449. // 监测年级
  450. var eg = examGradeDict[w.GradeId];
  451. // 学校目录
  452. if (fileName == "")
  453. {
  454. fileName = $"{w.SysOrgCode}-{w.SysOrgFullName}";
  455. }
  456. string orgDir = Path.Combine(fileRoot, fileName);
  457. if (!Directory.Exists(orgDir))
  458. {
  459. Directory.CreateDirectory(orgDir);
  460. }
  461. // 校区名称
  462. string branchName = "";
  463. if (w.SysOrgBranchId != null)
  464. {
  465. branchName = $"@{w.SysOrgBranchName}";
  466. }
  467. #region 导出抽中学生
  468. string sampleFileName = $"{w.SysOrgCode}-{w.SysOrgFullName}{branchName}-{eg.Grade.Name}-{ExamSampleType.DISTRICT.GetDescription()}-学生检录表";
  469. // 抽中学生列表
  470. var sampleStus = stus.Where(t => t.SysOrgId == w.SysOrgId && t.SysOrgBranchId == w.SysOrgBranchId && t.GradeId == w.GradeId && t.ExamSampleType == ExamSampleType.DISTRICT)
  471. .OrderBy(t => t.ExamNumber?[^4..]).ToList();
  472. // 自编号按导入顺序
  473. if (eg.IsRequiredSelfExamNumber)
  474. {
  475. sampleStus = sampleStus.OrderBy(t => t.ExamStudentId).ToList();
  476. }
  477. // 定义EXCEL列
  478. List<ExportExcelColDto<ExamSampleStudentExportDto>> cols = new()
  479. {
  480. new() { Name = "年级号", Width = 8, GetCellValue = (r) => r.GradeNumber },
  481. new() { Name = "班级号", Width = 8, GetCellValue = (r) => r.ClassNumber },
  482. new() { Name = "监测号", Width = 14, GetCellValue = (r) => r.ExamNumber },
  483. new() { Name = "姓名", Width = 20, GetCellValue = (r) => r.ExamStudentName },
  484. new() { Name = "证件类型", Width = 16, GetCellValue = (r) => certificateTypes[r.CertificateType] },
  485. new() { Name = "证件号码", Width = 20, GetCellValue = (r) => r.IdNumber },
  486. new() { Name = "名单类型", Width = 10, GetCellValue = (r) => r.ExamSampleType.GetDescription() },
  487. };
  488. if (w.SysOrgBranchId != null)
  489. {
  490. cols.Insert(0, new() { Name = "校区", Width = 10, GetCellValue = (r) => r.SysOrgBranchName });
  491. }
  492. // 导出EXCEL文件
  493. var ret = _exportExcelService.ExportExcel(new ExportExcelDto<ExamSampleStudentExportDto>()
  494. {
  495. Title = sampleFileName,
  496. Columns = cols,
  497. Items = sampleStus,
  498. IncludeExportTime = false,
  499. });
  500. // 写入文件
  501. await File.WriteAllBytesAsync(Path.Combine(orgDir, $"{sampleFileName}.xlsx"), ret);
  502. #endregion
  503. #region 导出未抽中学生
  504. string noSampleFileName = $"{w.SysOrgCode}-{w.SysOrgFullName}{branchName}-{eg.Grade.Name}-{ExamSampleType.SCHOOL_EXAM.GetDescription()}-学生检录表";
  505. // 抽中学生列表
  506. var noSampleStus = stus.Where(t => t.SysOrgId == w.SysOrgId && t.SysOrgBranchId == w.SysOrgBranchId && t.GradeId == w.GradeId && t.ExamSampleType == ExamSampleType.SCHOOL_EXAM)
  507. .OrderBy(t => t.ExamNumber?[^4..]).ToList();
  508. // 自编号按导入顺序
  509. if (eg.IsRequiredSelfExamNumber)
  510. {
  511. noSampleStus = noSampleStus.OrderBy(t => t.ExamStudentId).ToList();
  512. }
  513. // 定义EXCEL列
  514. List<ExportExcelColDto<ExamSampleStudentExportDto>> noCols = new()
  515. {
  516. new() { Name = "年级号", Width = 8, GetCellValue = (r) => r.GradeNumber },
  517. new() { Name = "班级号", Width = 8, GetCellValue = (r) => r.ClassNumber },
  518. new() { Name = "考号", Width = 14, GetCellValue = (r) => r.ExamNumber },
  519. new() { Name = "姓名", Width = 20, GetCellValue = (r) => r.ExamStudentName },
  520. new() { Name = "证件类型", Width = 16, GetCellValue = (r) => certificateTypes[r.CertificateType] },
  521. new() { Name = "证件号码", Width = 20, GetCellValue = (r) => r.IdNumber },
  522. new() { Name = "名单类型", Width = 10, GetCellValue = (r) => r.ExamSampleType.GetDescription() },
  523. };
  524. if (w.SysOrgBranchId != null)
  525. {
  526. noCols.Insert(0, new() { Name = "校区", Width = 10, GetCellValue = (r) => r.SysOrgBranchName });
  527. }
  528. // 导出EXCEL文件
  529. var noRet = _exportExcelService.ExportExcel(new ExportExcelDto<ExamSampleStudentExportDto>()
  530. {
  531. Title = noSampleFileName,
  532. Columns = noCols,
  533. Items = noSampleStus,
  534. IncludeExportTime = false,
  535. });
  536. // 写入文件
  537. await File.WriteAllBytesAsync(Path.Combine(orgDir, $"{noSampleFileName}.xlsx"), noRet);
  538. #endregion
  539. }
  540. // 学生目录
  541. var schoolFilePath = Path.Combine(fileRoot, fileName);
  542. // 统计表文件
  543. var countFile = await ExportSampleCountToOrg(id);
  544. await File.WriteAllBytesAsync(Path.Combine(schoolFilePath, $"{fileName}-监测抽样统计表.xlsx"), countFile.fileBytes);
  545. string outFileName = $"{plan.FullName}-{fileName}-监测抽样学生检录表.zip";
  546. string outFilePath = Path.Combine(fileRoot, outFileName);
  547. ICSharpCode.SharpZipLib.Zip.FastZip zip = new();
  548. zip.CreateZip(outFilePath, schoolFilePath, true, string.Empty);
  549. var retBytes = await File.ReadAllBytesAsync(outFilePath);
  550. Directory.Delete(fileRoot, true);
  551. return (outFileName, retBytes);
  552. }
  553. /// <summary>
  554. /// 导出抽样统计表
  555. /// </summary>
  556. /// <param name="id"></param>
  557. /// <returns></returns>
  558. public async Task<(string fileName, byte[] fileBytes)> ExportSampleCount(int id)
  559. {
  560. var examSample = await _rep.DetachedEntities.FirstOrDefaultAsync(t => t.Id == id) ?? throw Oops.Oh(ErrorCode.E2001);
  561. var items = await GetSampleCountListById(id);
  562. IWorkbook wb = new XSSFWorkbook();
  563. ISheet sheet = wb.CreateSheet();
  564. sheet.DisplayGridlines = false;
  565. // 获取样式
  566. var cellStyle = _exportExcelService.GetCellStyle(wb);
  567. #region 表头
  568. int rowNum = 0;
  569. IRow headerRow = sheet.CreateRow(rowNum++);
  570. headerRow.Height = ExportExcelCellStyle.DefaultRowHeight;
  571. int ci = 0;
  572. _exportExcelService.AddCell("数据类型", headerRow, ci++, cellStyle.ColumnHeaderStyle, sheet, 12);
  573. _exportExcelService.AddCell("学校代码", headerRow, ci++, cellStyle.ColumnHeaderStyle, sheet, 10);
  574. _exportExcelService.AddCell("学校名称", headerRow, ci++, cellStyle.ColumnHeaderStyle, sheet, 20);
  575. _exportExcelService.AddCell("年级", headerRow, ci++, cellStyle.ColumnHeaderStyle, sheet, 10);
  576. _exportExcelService.AddCell("班级", headerRow, ci++, cellStyle.ColumnHeaderStyle, sheet, 10);
  577. _exportExcelService.AddCell(ExamSampleType.DISTRICT.GetDescription(), headerRow, ci++, cellStyle.ColumnHeaderStyle, sheet, 12);
  578. _exportExcelService.AddCell(ExamSampleType.SCHOOL_EXAM.GetDescription(), headerRow, ci++, cellStyle.ColumnHeaderStyle, sheet, 12);
  579. _exportExcelService.AddCell("合计", headerRow, ci++, cellStyle.ColumnHeaderStyle, sheet, 12);
  580. sheet.CreateFreezePane(0, 1);
  581. #endregion
  582. foreach (var item in items)
  583. {
  584. IRow row = sheet.CreateRow(rowNum);
  585. row.Height = ExportExcelCellStyle.DefaultRowHeight;
  586. int si = 0;
  587. string schoolCode = item.SysOrgCode ?? "";
  588. string schoolName = item.SysOrgName ?? "";
  589. string gradeName = item.GradeName ?? "";
  590. long classNumber = item.ClassNumber;
  591. ICellStyle cstyle = cellStyle.CenterCellStyle;
  592. switch (item.TypeId)
  593. {
  594. case 2:
  595. si = 3;
  596. break;
  597. case 3:
  598. si = 2;
  599. break;
  600. case 4:
  601. si = 3;
  602. schoolCode = "";
  603. break;
  604. case 5:
  605. si = 2;
  606. schoolCode = "";
  607. break;
  608. }
  609. if (si > 0)
  610. {
  611. cstyle = cellStyle.FillCellStyle;
  612. sheet.AddMergedRegion(new CellRangeAddress(rowNum, rowNum, si, 4));
  613. }
  614. int rci = 0;
  615. _exportExcelService.AddCell(item.TypeName, row, rci++, cstyle);
  616. _exportExcelService.AddCell(schoolCode, row, rci++, cstyle);
  617. _exportExcelService.AddCell(schoolName, row, rci++, cstyle);
  618. _exportExcelService.AddCell(gradeName, row, rci++, cstyle);
  619. _exportExcelService.AddCell(classNumber, row, rci++, cstyle);
  620. _exportExcelService.AddCell(item.CenterStudentCount, row, rci++, cstyle);
  621. _exportExcelService.AddCell(item.SchoolStudentCount, row, rci++, cstyle);
  622. _exportExcelService.AddCell(item.TotalStudentCount, row, rci++, cstyle);
  623. rowNum++;
  624. }
  625. MemoryStream ms = new();
  626. wb.Write(ms, false);
  627. ms.Flush();
  628. return ($"{examSample.FullName}-监测抽样统计表.xlsx", ms.ToArray());
  629. }
  630. /// <summary>
  631. /// 导出学校抽样统计表
  632. /// </summary>
  633. /// <param name="id"></param>
  634. /// <returns></returns>
  635. public async Task<(string fileName, byte[] fileBytes)> ExportSampleCountToOrg(int id)
  636. {
  637. var examSample = await _rep.DetachedEntities.Include(t => t.ExamPlan).FirstOrDefaultAsync(t => t.Id == id) ?? throw Oops.Oh(ErrorCode.E2001);
  638. var items = await GetSampleCountListById(id);
  639. items = items.Where(t => t.TypeId < 4 && t.SysOrgId == CurrentSysUserInfo.SysOrgId).ToList();
  640. IWorkbook wb = new XSSFWorkbook();
  641. ISheet sheet = wb.CreateSheet();
  642. sheet.DisplayGridlines = false;
  643. // 获取样式
  644. var cellStyle = _exportExcelService.GetCellStyle(wb);
  645. #region 表头
  646. int rowNum = 0;
  647. IRow headerRow = sheet.CreateRow(rowNum++);
  648. headerRow.Height = ExportExcelCellStyle.DefaultRowHeight;
  649. int ci = 0;
  650. _exportExcelService.AddCell("数据类型", headerRow, ci++, cellStyle.ColumnHeaderStyle, sheet, 12);
  651. _exportExcelService.AddCell("学校代码", headerRow, ci++, cellStyle.ColumnHeaderStyle, sheet, 10);
  652. _exportExcelService.AddCell("学校名称", headerRow, ci++, cellStyle.ColumnHeaderStyle, sheet, 20);
  653. _exportExcelService.AddCell("年级", headerRow, ci++, cellStyle.ColumnHeaderStyle, sheet, 10);
  654. _exportExcelService.AddCell("班级", headerRow, ci++, cellStyle.ColumnHeaderStyle, sheet, 10);
  655. _exportExcelService.AddCell(ExamSampleType.DISTRICT.GetDescription(), headerRow, ci++, cellStyle.ColumnHeaderStyle, sheet, 12);
  656. _exportExcelService.AddCell(ExamSampleType.SCHOOL_EXAM.GetDescription(), headerRow, ci++, cellStyle.ColumnHeaderStyle, sheet, 12);
  657. _exportExcelService.AddCell("合计", headerRow, ci++, cellStyle.ColumnHeaderStyle, sheet, 12);
  658. sheet.CreateFreezePane(0, 1);
  659. #endregion
  660. foreach (var item in items)
  661. {
  662. IRow row = sheet.CreateRow(rowNum);
  663. row.Height = ExportExcelCellStyle.DefaultRowHeight;
  664. int si = 0;
  665. string schoolCode = item.SysOrgCode ?? "";
  666. string schoolName = item.SysOrgName ?? "";
  667. string gradeName = item.GradeName ?? "";
  668. long classNumber = item.ClassNumber;
  669. ICellStyle cstyle = cellStyle.CenterCellStyle;
  670. switch (item.TypeId)
  671. {
  672. case 2:
  673. si = 3;
  674. break;
  675. case 3:
  676. si = 2;
  677. break;
  678. case 4:
  679. si = 3;
  680. schoolCode = "";
  681. break;
  682. case 5:
  683. si = 2;
  684. schoolCode = "";
  685. break;
  686. }
  687. if (si > 0)
  688. {
  689. cstyle = cellStyle.FillCellStyle;
  690. sheet.AddMergedRegion(new CellRangeAddress(rowNum, rowNum, si, 4));
  691. }
  692. int rci = 0;
  693. _exportExcelService.AddCell(item.TypeName, row, rci++, cstyle);
  694. _exportExcelService.AddCell(schoolCode, row, rci++, cstyle);
  695. _exportExcelService.AddCell(schoolName, row, rci++, cstyle);
  696. _exportExcelService.AddCell(gradeName, row, rci++, cstyle);
  697. _exportExcelService.AddCell(classNumber, row, rci++, cstyle);
  698. _exportExcelService.AddCell(item.CenterStudentCount, row, rci++, cstyle);
  699. _exportExcelService.AddCell(item.SchoolStudentCount, row, rci++, cstyle);
  700. _exportExcelService.AddCell(item.TotalStudentCount, row, rci++, cstyle);
  701. rowNum++;
  702. }
  703. MemoryStream ms = new();
  704. wb.Write(ms, false);
  705. ms.Flush();
  706. return ($"{examSample.ExamPlan.FullName}-{CurrentSysUserInfo.SysOrgName}-监测抽样统计表.xlsx", ms.ToArray());
  707. }
  708. #endregion
  709. #region 方案查询
  710. /// <summary>
  711. /// 根据ID获取抽样方案
  712. /// </summary>
  713. /// <param name="id"></param>
  714. /// <returns></returns>
  715. public async Task<ExamSampleOutput> GetById(int id)
  716. {
  717. var item = await _rep.DetachedEntities.ProjectToType<ExamSampleOutput>().FirstOrDefaultAsync(t => t.Id == id);
  718. return item.Adapt<ExamSampleOutput>();
  719. }
  720. /// <summary>
  721. /// 根据监测计划ID获取全部抽样方案
  722. /// </summary>
  723. /// <param name="examPlanId"></param>
  724. /// <returns></returns>
  725. public async Task<List<ExamSampleOutput>> GetListByExamPlanId(int examPlanId)
  726. {
  727. var items = await _rep.DetachedEntities.Where(t => t.ExamPlanId == examPlanId).ProjectToType<ExamSampleOutput>().ToListAsync();
  728. return items;
  729. }
  730. /// <summary>
  731. /// 获取抽样统计表
  732. /// </summary>
  733. /// <param name="id"></param>
  734. /// <returns></returns>
  735. public async Task<List<ExamSampleCountOutput>> GetSampleCountListById(int id)
  736. {
  737. var examSample = await _rep.DetachedEntities.FirstOrDefaultAsync(t => t.Id == id) ?? throw Oops.Oh(ErrorCode.E2001);
  738. string whereSql = $"WHERE T1.exam_plan_id = {examSample.ExamPlanId} AND T2.exam_sample_id = {id}";
  739. string querySql = @$"
  740. SELECT
  741. T1.type_id,
  742. T3.`name` AS type_name,
  743. T1.sys_org_id,
  744. T2.`code` AS sys_org_code,
  745. T2.`name` AS sys_org_name,
  746. T2.full_name AS sys_org_full_name,
  747. T1.grade_id,
  748. T4.`name` AS grade_name,
  749. T1.school_class_id,
  750. T1.class_number,
  751. T1.total_student_count,
  752. T1.center_student_count,
  753. T1.school_student_count
  754. FROM
  755. (
  756. -- 班级汇总
  757. SELECT 1 AS type_id, T.sys_org_id, T.grade_id, T.school_class_id, T.class_number,
  758. SUM(CASE WHEN T.exam_sample_type = @district THEN T.student_count ELSE 0 END) AS center_student_count,
  759. SUM(CASE WHEN T.exam_sample_type = @schoolExam THEN T.student_count ELSE 0 END) AS school_student_count,
  760. SUM(T.student_count) AS total_student_count
  761. FROM
  762. (
  763. SELECT T1.sys_org_id, T1.grade_id, T1.school_class_id, T1.class_number, T2.exam_sample_type, COUNT(1) AS student_count
  764. FROM exam_student AS T1
  765. JOIN exam_sample_student AS T2 ON T1.id = T2.exam_student_id
  766. {whereSql}
  767. GROUP BY T1.sys_org_id, T1.grade_id, T1.school_class_id, T1.class_number, T2.exam_sample_type
  768. ) AS T
  769. GROUP BY T.sys_org_id, T.grade_id, T.school_class_id, T.class_number
  770. UNION ALL
  771. -- 年级汇总
  772. SELECT 2 AS type_id, T.sys_org_id, T.grade_id, NULL AS school_class_id, 9999 AS class_number,
  773. SUM(CASE WHEN T.exam_sample_type = @district THEN T.student_count ELSE 0 END) AS center_student_count,
  774. SUM(CASE WHEN T.exam_sample_type = @schoolExam THEN T.student_count ELSE 0 END) AS school_student_count,
  775. SUM(T.student_count) AS total_student_count
  776. FROM
  777. (
  778. SELECT T1.sys_org_id, T1.grade_id, T2.exam_sample_type, COUNT(1) AS student_count
  779. FROM exam_student AS T1
  780. JOIN exam_sample_student AS T2 ON T1.id = T2.exam_student_id
  781. {whereSql}
  782. GROUP BY T1.sys_org_id, T1.grade_id, T2.exam_sample_type
  783. ) AS T
  784. GROUP BY T.sys_org_id, T.grade_id
  785. UNION ALL
  786. -- 学校汇总
  787. SELECT 3 AS type_id, T.sys_org_id, 9999 AS grade_id, NULL AS school_class_id, 9999 AS class_number,
  788. SUM(CASE WHEN T.exam_sample_type = @district THEN T.student_count ELSE 0 END) AS center_student_count,
  789. SUM(CASE WHEN T.exam_sample_type = @schoolExam THEN T.student_count ELSE 0 END) AS school_student_count,
  790. SUM(T.student_count) AS total_student_count
  791. FROM
  792. (
  793. SELECT T1.sys_org_id, T2.exam_sample_type, COUNT(1) AS student_count
  794. FROM exam_student AS T1
  795. JOIN exam_sample_student AS T2 ON T1.id = T2.exam_student_id
  796. {whereSql}
  797. GROUP BY T1.sys_org_id, T2.exam_sample_type
  798. ) AS T
  799. GROUP BY T.sys_org_id
  800. UNION ALL
  801. -- 全区年级汇总
  802. SELECT 4 AS type_id, 9999 AS sys_org_id, T.grade_id, NULL AS school_class_id, 9999 AS class_number,
  803. SUM(CASE WHEN T.exam_sample_type = @district THEN T.student_count ELSE 0 END) AS center_student_count,
  804. SUM(CASE WHEN T.exam_sample_type = @schoolExam THEN T.student_count ELSE 0 END) AS school_student_count,
  805. SUM(T.student_count) AS total_student_count
  806. FROM
  807. (
  808. SELECT T1.grade_id, T2.exam_sample_type, COUNT(1) AS student_count
  809. FROM exam_student AS T1
  810. JOIN exam_sample_student AS T2 ON T1.id = T2.exam_student_id
  811. {whereSql}
  812. GROUP BY T1.grade_id, T2.exam_sample_type
  813. ) AS T
  814. GROUP BY T.grade_id
  815. UNION ALL
  816. -- 全区汇总
  817. SELECT 5 AS type_id, 9999 AS sys_org_id, 9999 AS grade_id, NULL AS school_class_id, 9999 AS class_number,
  818. SUM(CASE WHEN T.exam_sample_type = @district THEN T.student_count ELSE 0 END) AS center_student_count,
  819. SUM(CASE WHEN T.exam_sample_type = @schoolExam THEN T.student_count ELSE 0 END) AS school_student_count,
  820. SUM(T.student_count) AS total_student_count
  821. FROM
  822. (
  823. SELECT T2.exam_sample_type, COUNT(1) AS student_count
  824. FROM exam_student AS T1
  825. JOIN exam_sample_student AS T2 ON T1.id = T2.exam_student_id
  826. {whereSql}
  827. GROUP BY T2.exam_sample_type
  828. ) AS T
  829. ) AS T1
  830. LEFT JOIN
  831. (
  832. SELECT id, `code`, `name`, full_name FROM sys_org WHERE org_type = 3
  833. UNION ALL
  834. SELECT 9999 AS id, '999' AS `code`, '全区' AS `name`, '全区' AS full_name
  835. ) AS T2 ON T1.sys_org_id = T2.id
  836. JOIN
  837. (
  838. SELECT 1 AS id, '学校班级' AS `name` UNION ALL
  839. SELECT 2 AS id, '学校年级' AS `name` UNION ALL
  840. SELECT 3 AS id, '学校' AS `name` UNION ALL
  841. SELECT 4 AS id, '全区年级' AS `name` UNION ALL
  842. SELECT 5 AS id, '全区' AS `name`
  843. ) AS T3 ON T1.type_id = T3.id
  844. LEFT JOIN base_grade AS T4 ON T1.grade_id = T4.id
  845. ORDER BY T2.`code`, T1.grade_id, T1.class_number, T1.type_id
  846. ";
  847. var items = await _rep.SqlQueriesAsync<ExamSampleCountOutput>(querySql, new
  848. {
  849. District = (short)ExamSampleType.DISTRICT,
  850. SchoolExam = (short)ExamSampleType.SCHOOL_EXAM
  851. });
  852. return items;
  853. }
  854. /// <summary>
  855. /// 获取学校抽样统计表
  856. /// </summary>
  857. /// <param name="id"></param>
  858. /// <returns></returns>
  859. public async Task<List<ExamSampleCountOutput>> GetOrgSampleCountListById(int id)
  860. {
  861. var items = await GetSampleCountListById(id);
  862. items = items.Where(t => t.TypeId < 4 && t.SysOrgId == CurrentSysUserInfo.SysOrgId).ToList();
  863. return items;
  864. }
  865. /// <summary>
  866. /// 查询已发布抽样
  867. /// </summary>
  868. /// <param name="examDataPublishId">监测发布内容ID</param>
  869. /// <param name="type">抽样数据发布类型</param>
  870. /// <returns></returns>
  871. public async Task<ExamSamplePlanOutput> GetByExamDataPublishId(int examDataPublishId, DataPublishType type)
  872. {
  873. if(type != DataPublishType.STUDENT_SAMPLE_LIST && type != DataPublishType.STUDENT_SAMPLE_COUNT_LIST)
  874. {
  875. throw Oops.Oh(ErrorCode.E1014, "数据发布");
  876. }
  877. var pub = await _rep.Change<ExamDataPublish>().DetachedEntities.FirstOrDefaultAsync(t => t.Id == examDataPublishId && t.Type == type) ?? throw Oops.Oh(ErrorCode.E2001, "反馈内容");
  878. var item = await _rep.DetachedEntities.Include(t => t.ExamPlan)
  879. .FirstOrDefaultAsync(t => t.ExamPlanId == pub.ExamPlanId && t.ExamPlan.IsFixedExamSample == true && t.Status == ExamSampleStatus.SUCCESSFUL && t.IsSelected == true)
  880. ?? throw Oops.Oh(ErrorCode.E2001, "抽样方案");
  881. return item.Adapt<ExamSamplePlanOutput>();
  882. }
  883. #endregion
  884. #region 机构查询
  885. #endregion
  886. #region 私有方法
  887. /// <summary>
  888. /// 获取最终抽样学生列表
  889. /// </summary>
  890. /// <param name="examSample"></param>
  891. /// <param name="examGradeDict"></param>
  892. /// <param name="stus"></param>
  893. /// <returns></returns>
  894. private static List<AddExamSampleStudentInput> GetFinalSampleList(ExamSampleOutput examSample, Dictionary<short, ExamGradeOutput> examGradeDict, List<ExamSampleDto> stus)
  895. {
  896. // 抽样配置
  897. var config = examSample.Config;
  898. // 返回结果集
  899. List<AddExamSampleStudentInput> retItems = new();
  900. // 遍历集合
  901. var ws = stus.Select(t => new { t.SysOrgId, t.SysOrgBranchId, t.GradeId, t.SchoolClassId }).Distinct().ToList();
  902. // 学校
  903. var orgs = ws.Select(t => t.SysOrgId).Distinct().ToList();
  904. foreach (var orgId in orgs)
  905. {
  906. // 跳过不参与监测的学校
  907. if (config.ExcludeSysOrgIds.Contains(orgId))
  908. {
  909. continue;
  910. }
  911. // 校区
  912. var branches = ws.Where(t => t.SysOrgId == orgId).Select(t => t.SysOrgBranchId).Distinct().ToList();
  913. foreach (var branchId in branches)
  914. {
  915. // 年级
  916. var grades = ws.Where(t => t.SysOrgId == orgId && t.SysOrgBranchId == branchId).Select(t => t.GradeId).Distinct().ToList();
  917. foreach (var gradeId in grades)
  918. {
  919. var eg = examGradeDict[gradeId];
  920. // 自编号直接写入
  921. if (eg.IsRequiredSelfExamNumber)
  922. {
  923. var selfList = stus.Where(t => t.SysOrgId == orgId && t.SysOrgBranchId == branchId && t.GradeId == gradeId && t.IsSelfExamNumber == true).OrderBy(t => t.ExamNumber?[^4..]).ToList();
  924. retItems.AddRange(selfList.Select((t, i) => new AddExamSampleStudentInput
  925. {
  926. ExamSampleId = examSample.Id,
  927. ExamStudentId = t.ExamStudentId,
  928. ExamNumber = t.ExamNumber,
  929. ExamSampleType = t.ExamSampleType ?? ExamSampleType.DISTRICT,
  930. IsSpecialStudent = t.IsSpecialStudent,
  931. PreTotalScore = t.TotalScore ?? 0,
  932. Sequence = i + 1,
  933. }));
  934. continue;
  935. }
  936. // 区测学生列表
  937. var sampleStus = stus.Where(t => t.SysOrgId == orgId && t.SysOrgBranchId == branchId && t.GradeId == gradeId && t.ExamSampleType == ExamSampleType.DISTRICT)
  938. .OrderBy(t => t.ClassNumber).ThenBy(t => t.Sequence).ToList();
  939. // 区测学生数量
  940. var sampleCount = sampleStus.Count;
  941. // 是否在年级内随机打乱顺序
  942. if (config.IsGradeSeatNumberRandom)
  943. {
  944. Utils.ListRandom(sampleStus);
  945. }
  946. // 生成区测学生监测号并插入
  947. retItems.AddRange(sampleStus.Select((t, i) => new AddExamSampleStudentInput
  948. {
  949. ExamSampleId = examSample.Id,
  950. ExamStudentId = t.ExamStudentId,
  951. ExamNumber = BuildExamNumber(t.SysOrgCode, eg.Grade.GradeNumber, t.ClassNumber, i + 1),
  952. ExamSampleType = ExamSampleType.DISTRICT,
  953. IsSpecialStudent = t.IsSpecialStudent,
  954. PreTotalScore = t.TotalScore ?? 0,
  955. Sequence = i + 1,
  956. }));
  957. // 校测学生列表
  958. var noSampleStus = stus.Where(t => t.SysOrgId == orgId && t.SysOrgBranchId == branchId && t.GradeId == gradeId && t.ExamSampleType == ExamSampleType.SCHOOL_EXAM)
  959. .OrderBy(t => t.ClassNumber).ThenBy(t => t.Sequence).ToList();
  960. // 是否在年级内随机打乱顺序
  961. if (config.IsGradeSeatNumberRandom)
  962. {
  963. Utils.ListRandom(noSampleStus);
  964. }
  965. // 生成校测学生监测号并插入
  966. retItems.AddRange(noSampleStus.Select((t, i) => new AddExamSampleStudentInput
  967. {
  968. ExamSampleId = examSample.Id,
  969. ExamStudentId = t.ExamStudentId,
  970. ExamNumber = BuildExamNumber(t.SysOrgCode, eg.Grade.GradeNumber, t.ClassNumber, sampleCount + 100 + i + 1),
  971. ExamSampleType = ExamSampleType.SCHOOL_EXAM,
  972. IsSpecialStudent = t.IsSpecialStudent,
  973. PreTotalScore = t.TotalScore ?? 0,
  974. Sequence = i + 1,
  975. }));
  976. }
  977. }
  978. }
  979. return retItems;
  980. }
  981. /// <summary>
  982. /// 生成监测号
  983. /// </summary>
  984. /// <param name="sysOrgCode">学校代码</param>
  985. /// <param name="gradeNumber">年级序号</param>
  986. /// <param name="classNumber">班级序号</param>
  987. /// <param name="sequence">顺序号</param>
  988. /// <returns></returns>
  989. private static string BuildExamNumber(string sysOrgCode, short gradeNumber, short classNumber, int sequence)
  990. {
  991. return $"{sysOrgCode.PadLeft(3, '0')}{gradeNumber.ToString().PadLeft(2, '0')}{classNumber.ToString().PadLeft(2, '0')}{sequence.ToString().PadLeft(4, '0')}";
  992. }
  993. /// <summary>
  994. /// 获取抽样名单
  995. /// </summary>
  996. /// <param name="config">抽样配置</param>
  997. /// <param name="examGradeDict">监测年级字典,键为年级ID</param>
  998. /// <param name="stus">学生列表</param>
  999. /// <returns></returns>
  1000. private static List<ExamSampleDto> GetSampleList(ExamSampleConfig config, Dictionary<short, ExamGradeOutput> examGradeDict, List<ExamSampleDto> stus)
  1001. {
  1002. // 抽样比例
  1003. var sampleRate = config.Percent / 100.0;
  1004. // 返回结果集
  1005. List<ExamSampleDto> retItems = new();
  1006. // 遍历集合
  1007. var ws = stus.Select(t => new { t.SysOrgId, t.SysOrgBranchId, t.GradeId, t.SchoolClassId }).Distinct().ToList();
  1008. // 学校
  1009. var orgs = ws.Select(t => t.SysOrgId).Distinct().ToList();
  1010. foreach (var orgId in orgs)
  1011. {
  1012. // 跳过不参与监测的学校
  1013. if (config.ExcludeSysOrgIds.Contains(orgId))
  1014. {
  1015. continue;
  1016. }
  1017. // 校区
  1018. var branches = ws.Where(t => t.SysOrgId == orgId).Select(t => t.SysOrgBranchId).Distinct().ToList();
  1019. foreach (var branchId in branches)
  1020. {
  1021. // 年级
  1022. var grades = ws.Where(t => t.SysOrgId == orgId && t.SysOrgBranchId == branchId).Select(t => t.GradeId).Distinct().ToList();
  1023. foreach (var gradeId in grades)
  1024. {
  1025. // 班级
  1026. var classes = ws.Where(t => t.SysOrgId == orgId && t.SysOrgBranchId == branchId && t.GradeId == gradeId).Select(t => t.SchoolClassId).Distinct().Order().ToList();
  1027. var classCount = classes.Count;
  1028. // 1.年级自编号直接写入
  1029. var eg = examGradeDict[gradeId];
  1030. if (eg != null && eg.IsRequiredSelfExamNumber)
  1031. {
  1032. var items = stus.Where(t => t.SysOrgId == orgId && t.SysOrgBranchId == branchId && t.GradeId == gradeId).ToList();
  1033. foreach (var item in items)
  1034. {
  1035. item.ExamSampleType = ExamSampleType.DISTRICT;
  1036. item.IsSelfExamNumber = true;
  1037. item.ExamNumber = item.ExamNumber.Trim().Replace(" ", "");
  1038. //if (item.ExamNumber.Length != eg.SelfExamNumberLength)
  1039. //{
  1040. // throw Oops.Oh(ErrorCode.E1013, $"{item.ExamStudentId},{item.ExamStudentName},{item.ExamNumber}");
  1041. //}
  1042. }
  1043. retItems.AddRange(items.Where(t => t.ExamNumber.Length >= 4));
  1044. continue;
  1045. }
  1046. // 2.年级只有一个班,处理最低人数限制,满足条件该年级全抽
  1047. if (config.IsEnabledOnlyOneClassStudentMin && classCount == 1)
  1048. {
  1049. var stuCount = stus.Count(t => t.SysOrgId == orgId && t.SysOrgBranchId == branchId && t.GradeId == gradeId);
  1050. if (stuCount <= config.OnlyOneClassStudentMin)
  1051. {
  1052. var items = stus.Where(t => t.SysOrgId == orgId && t.SysOrgBranchId == branchId && t.GradeId == gradeId).ToList();
  1053. for (int i = 0; i < items.Count; i++)
  1054. {
  1055. items[i].ExamSampleType = ExamSampleType.DISTRICT;
  1056. items[i].Sequence = i + 1;
  1057. }
  1058. retItems.AddRange(items);
  1059. continue;
  1060. }
  1061. }
  1062. // 3.年级多于一个班,处理年级未抽样最低限制,满足条件该年级全抽
  1063. if (config.IsEnabledGradeNoSampleStudentMin && classCount > 1)
  1064. {
  1065. var classStuCount = stus.Where(t => t.SysOrgId == orgId && t.SysOrgBranchId == branchId && t.GradeId == gradeId).GroupBy(t => t.SchoolClassId).Select(t => new { t.Key, Count = t.Count() }).ToList();
  1066. if (classStuCount.Sum(t => Math.Ceiling(t.Count * (1 - sampleRate))) <= config.GradeNoSampleStudentMin)
  1067. {
  1068. var items = stus.Where(t => t.SysOrgId == orgId && t.SysOrgBranchId == branchId && t.GradeId == gradeId).ToList();
  1069. for (int i = 0; i < items.Count; i++)
  1070. {
  1071. items[i].ExamSampleType = ExamSampleType.DISTRICT;
  1072. items[i].Sequence = i + 1;
  1073. }
  1074. retItems.AddRange(items);
  1075. continue;
  1076. }
  1077. }
  1078. // 遍历处理各班级
  1079. foreach (var classId in classes)
  1080. {
  1081. // 班级学生数量
  1082. var classStuCount = stus.Count(t => t.SysOrgId == orgId && t.SysOrgBranchId == branchId && t.GradeId == gradeId && t.SchoolClassId == classId);
  1083. if (classStuCount == 0)
  1084. {
  1085. continue;
  1086. }
  1087. // 4.班级设置为全抽的直接写入
  1088. // 5.班级学生人数低于限制
  1089. if (config.SampleAllSchoolClassIds.Contains(classId) || (config.IsEnabledClassStudentMin && classStuCount <= config.ClassStudentMin))
  1090. {
  1091. var items = stus.Where(t => t.SysOrgId == orgId && t.SysOrgBranchId == branchId && t.GradeId == gradeId && t.SchoolClassId == classId).ToList();
  1092. for (int i = 0; i < items.Count; i++)
  1093. {
  1094. items[i].ExamSampleType = ExamSampleType.DISTRICT;
  1095. items[i].Sequence = i + 1;
  1096. }
  1097. retItems.AddRange(items);
  1098. continue;
  1099. }
  1100. // -------------------------
  1101. // 以下是需要抽样的
  1102. // -------------------------
  1103. // 班级学生列表
  1104. var classStus = stus.Where(t => t.SysOrgId == orgId && t.SysOrgBranchId == branchId && t.GradeId == gradeId && t.SchoolClassId == classId).ToList();
  1105. // 抽中学生列表
  1106. List<ExamSampleDto> sampleStus = new();
  1107. // 该班是否有成绩
  1108. var hasScore = classStus.Any(t => t.TotalScore != null && t.TotalScore > 0);
  1109. // 最大抽样数量
  1110. var maxSampleStuCount = (int)Math.Ceiling(classStuCount * sampleRate);
  1111. // 排除特殊学生
  1112. if (config.IsExcludeSpecialStudent)
  1113. {
  1114. // 非特殊学生列表
  1115. var normalStus = stus.Where(t => t.SysOrgId == orgId && t.SysOrgBranchId == branchId && t.GradeId == gradeId && t.SchoolClassId == classId && t.IsSpecialStudent == false).ToList();
  1116. // 特殊学生数量
  1117. var classSpecialStuCount = stus.Count(t => t.SysOrgId == orgId && t.SysOrgBranchId == branchId && t.GradeId == gradeId && t.SchoolClassId == classId && t.IsSpecialStudent == true);
  1118. // 特殊学生数量占比大于等于 1-抽样比例,非特殊学生全抽
  1119. if (classSpecialStuCount / classStuCount >= (1 - sampleRate))
  1120. {
  1121. // 非特殊学生
  1122. for (int i = 0; i < normalStus.Count; i++)
  1123. {
  1124. normalStus[i].ExamSampleType = ExamSampleType.DISTRICT;
  1125. normalStus[i].Sequence = i + 1;
  1126. }
  1127. retItems.AddRange(normalStus);
  1128. }
  1129. // 仅非特殊学生参与抽样
  1130. else
  1131. {
  1132. // 有成绩的循环抽样
  1133. if (hasScore)
  1134. {
  1135. int sq = 1;
  1136. CyclicSampling(config, normalStus, sampleStus, maxSampleStuCount, sq);
  1137. retItems.AddRange(sampleStus);
  1138. }
  1139. // 无成绩随机抽样
  1140. else
  1141. {
  1142. sampleStus = RandomSampling(normalStus, classStuCount, sampleRate);
  1143. retItems.AddRange(sampleStus);
  1144. }
  1145. }
  1146. }
  1147. // 不排除特殊学生
  1148. else
  1149. {
  1150. // 有成绩的循环抽样
  1151. if (hasScore)
  1152. {
  1153. int sq = 1;
  1154. CyclicSampling(config, classStus, sampleStus, maxSampleStuCount, sq);
  1155. retItems.AddRange(sampleStus);
  1156. }
  1157. // 无成绩随机抽样
  1158. else
  1159. {
  1160. sampleStus = RandomSampling(classStus, classStuCount, sampleRate);
  1161. retItems.AddRange(sampleStus);
  1162. }
  1163. }
  1164. // 未抽中学生列表
  1165. var noSampleStus = (from s in classStus where !(from n in sampleStus select n.ExamStudentId).Contains(s.ExamStudentId) select s).ToList();
  1166. for (int i = 0; i < noSampleStus.Count; i++)
  1167. {
  1168. noSampleStus[i].ExamSampleType = ExamSampleType.SCHOOL_EXAM;
  1169. noSampleStus[i].Sequence = i + 1;
  1170. }
  1171. retItems.AddRange(noSampleStus);
  1172. }
  1173. }
  1174. }
  1175. }
  1176. return retItems;
  1177. }
  1178. /// <summary>
  1179. /// 循环抽样
  1180. /// </summary>
  1181. /// <param name="config"></param>
  1182. /// <param name="stus"></param>
  1183. /// <param name="sampleStus"></param>
  1184. /// <param name="maxStuCount"></param>
  1185. /// <param name="sequence"></param>
  1186. private static void CyclicSampling(ExamSampleConfig config, List<ExamSampleDto> stus, List<ExamSampleDto> sampleStus, int maxStuCount, int sequence)
  1187. {
  1188. for (int i = config.StartPosition >= stus.Count ? 0 : config.StartPosition - 1; i < stus.Count; i += config.Interval)
  1189. {
  1190. var stu = stus[i];
  1191. var nstus = stu.Adapt<ExamSampleDto>();
  1192. nstus.Sequence = sequence++;
  1193. nstus.ExamSampleType = ExamSampleType.DISTRICT;
  1194. sampleStus.Add(nstus);
  1195. // 如果已抽满结束抽样
  1196. if (sampleStus.Count >= maxStuCount)
  1197. {
  1198. break;
  1199. }
  1200. }
  1201. if (sampleStus.Count < maxStuCount)
  1202. {
  1203. var noSampleStus = (from s in stus where !(from n in sampleStus select n.ExamStudentId).Contains(s.ExamStudentId) select s).ToList();
  1204. CyclicSampling(config, noSampleStus, sampleStus, maxStuCount, sequence);
  1205. }
  1206. }
  1207. /// <summary>
  1208. /// 随机抽样
  1209. /// </summary>
  1210. /// <param name="stus"></param>
  1211. /// <param name="classStuCount"></param>
  1212. /// <param name="sampleRate"></param>
  1213. /// <returns></returns>
  1214. private static List<ExamSampleDto> RandomSampling(List<ExamSampleDto> stus, int classStuCount, double sampleRate)
  1215. {
  1216. List<ExamSampleDto> sampleStus = new();
  1217. var sampleIndexList = GetRandomIndex(classStuCount, sampleRate);
  1218. int sq = 1;
  1219. for (int i = 0; i < stus.Count; i++)
  1220. {
  1221. if (sampleIndexList.Contains(i))
  1222. {
  1223. var item = stus[i].Adapt<ExamSampleDto>();
  1224. item.ExamSampleType = ExamSampleType.DISTRICT;
  1225. item.Sequence = sq++;
  1226. sampleStus.Add(item);
  1227. }
  1228. }
  1229. return sampleStus;
  1230. }
  1231. /// <summary>
  1232. /// 获取随机位置列表
  1233. /// </summary>
  1234. /// <param name="totalCount">总数量</param>
  1235. /// <param name="sampleRate">抽样比例</param>
  1236. /// <returns></returns>
  1237. private static List<int> GetRandomIndex(int totalCount, double sampleRate)
  1238. {
  1239. Random random = new();
  1240. List<int> samples = new();
  1241. int i = 0;
  1242. var sc = Math.Ceiling(totalCount * sampleRate);
  1243. while (i < sc)
  1244. {
  1245. int s = random.Next(totalCount);
  1246. if (samples.Any(t => t == s))
  1247. {
  1248. continue;
  1249. }
  1250. samples.Add(s);
  1251. i++;
  1252. }
  1253. return samples.OrderBy(t => t).ToList();
  1254. }
  1255. /// <summary>
  1256. /// 获取学生并带往期成绩
  1257. /// </summary>
  1258. /// <param name="config">抽样配置</param>
  1259. /// <param name="examPlanId">抽样监测计划ID</param>
  1260. /// <param name="grades">年级ID列表</param>
  1261. /// <returns></returns>
  1262. private async Task<List<ExamSampleDto>> GetStudentScoreList(ExamSampleConfig config, int examPlanId, List<short> grades)
  1263. {
  1264. string gradeWhere = string.Join(" OR ", grades.Select(t => $"T1.grade_id = {t}"));
  1265. string selectSql = $@"
  1266. SELECT
  1267. T1.id AS exam_student_id,
  1268. T1.`name` AS exam_student_name,
  1269. T1.certificate_type,
  1270. UPPER(T1.id_number) AS id_number,
  1271. T1.sys_org_id,
  1272. ORG.`code` AS sys_org_code,
  1273. T1.sys_org_branch_id,
  1274. T1.grade_id,
  1275. T1.school_class_id,
  1276. T1.class_number,
  1277. T1.exam_number,
  1278. T2.course_count,
  1279. T2.total_score,
  1280. CASE WHEN ISNULL(T3.id) THEN 0 ELSE 1 END as is_special_student
  1281. FROM exam_student AS T1
  1282. LEFT JOIN
  1283. (
  1284. -- 往期总分
  1285. SELECT T1.sys_org_id, T1.school_class_id, T2.certificate_type, UPPER(T2.id_number) AS id_number, COUNT(T1.id) AS course_count, SUM(T1.score) AS total_score
  1286. FROM exam_score AS T1
  1287. JOIN exam_student AS T2 ON T1.exam_plan_id = T2.exam_plan_id AND T1.exam_student_id = T2.id
  1288. WHERE T1.exam_plan_id = @examSampleRefExamPlanId
  1289. GROUP BY T1.sys_org_id, T1.school_class_id, T2.certificate_type, T2.id_number
  1290. ) AS T2
  1291. -- ON T1.sys_org_id = T2.sys_org_id AND T1.school_class_id = T2.school_class_id AND T1.certificate_type = T2.certificate_type AND UPPER(T1.id_number) = T2.id_number
  1292. ON T1.sys_org_id = T2.sys_org_id AND T1.school_class_id = T2.school_class_id AND UPPER(T1.id_number) = T2.id_number
  1293. JOIN
  1294. (
  1295. SELECT DISTINCT exam_plan_id, sys_org_id
  1296. FROM exam_org
  1297. WHERE exam_plan_id = @examPlanId AND is_required_exam = 1
  1298. ) AS EO ON T1.exam_plan_id = EO.exam_plan_id AND T1.sys_org_id = EO.sys_org_id
  1299. JOIN sys_org AS ORG ON T1.sys_org_id = ORG.id
  1300. -- LEFT JOIN special_student AS T3 ON T1.sys_org_id = T3.sys_org_id AND T1.certificate_type = T3.certificate_type AND UPPER(T1.id_number) = UPPER(T3.id_number)
  1301. LEFT JOIN
  1302. (
  1303. SELECT id, exam_plan_id, sys_org_id, certificate_type, id_number
  1304. FROM exam_special_student
  1305. WHERE exam_plan_id = @examPlanId AND `status` = @specialStudentStatus
  1306. -- ) AS T3 ON T1.exam_plan_id = T3.exam_plan_id AND T1.sys_org_id = T3.sys_org_id AND T1.certificate_type = T3.certificate_type AND UPPER(T1.id_number) = UPPER(T3.id_number)
  1307. ) AS T3 ON T1.exam_plan_id = T3.exam_plan_id AND T1.sys_org_id = T3.sys_org_id AND UPPER(T1.id_number) = UPPER(T3.id_number)
  1308. WHERE T1.exam_plan_id = @examPlanId AND ({gradeWhere})
  1309. ORDER BY T1.sys_org_id, T1.grade_id, T1.school_class_id, T2.total_score DESC, T1.id
  1310. ";
  1311. var items = await _rep.SqlQueriesAsync<ExamSampleDto>(selectSql, new
  1312. {
  1313. ExamSampleRefExamPlanId = config.ExamSampleRefExamPlanId ?? 0,
  1314. ExamPlanId = examPlanId,
  1315. SpecialStudentStatus = (short)AuditStatus.APPROVED,
  1316. });
  1317. return items;
  1318. }
  1319. #endregion
  1320. }