ExamSampleService.cs 64 KB

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