ExamSampleService.cs 84 KB

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