ExamScoreImportService.cs 41 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950
  1. using Furion.ClayObject.Extensions;
  2. using Furion.DatabaseAccessor.Extensions;
  3. using NPOI.SS.UserModel;
  4. using NPOI.XSSF.UserModel;
  5. using System.IO.Compression;
  6. using YBEE.EQM.Core;
  7. namespace YBEE.EQM.Application;
  8. /// <summary>
  9. /// 学生成绩导入服务
  10. /// </summary>
  11. public class ExamScoreImportService(IRepository<ExamScore> rep, ISchoolClassService schoolClassService, IExamGradeService examGradeService, IExportExcelService exportExcelService) : IExamScoreImportService, ITransient
  12. {
  13. private readonly IRepository<ExamScore> _rep = rep;
  14. private readonly ISchoolClassService _schoolClassService = schoolClassService;
  15. private readonly IExamGradeService _examGradeService = examGradeService;
  16. private readonly IExportExcelService _exportExcelService = exportExcelService;
  17. /// <summary>
  18. /// 批量导入前期未上报学生名单的各科成绩(初始化)
  19. /// </summary>
  20. /// <param name="filePath"></param>
  21. /// <param name="examPlanId"></param>
  22. /// <returns></returns>
  23. public async Task UploadImportWithoutStudentTotalScore(string filePath, int examPlanId)
  24. {
  25. /* ----------------------------------------------------------
  26. * 处理步骤:
  27. * 1.验证表头
  28. * 2.读取数据
  29. * 3.处理班级
  30. * 4.处理学生
  31. * 5.批量插入
  32. ---------------------------------------------------------- */
  33. try
  34. {
  35. #region 1.验证表结构
  36. using FileStream fs = new(filePath, FileMode.Open, FileAccess.Read);
  37. IWorkbook workbook = ExcelUtil.GetWorkbook(filePath, fs);
  38. var sheet = workbook.GetSheetAt(0);
  39. var rows = sheet.GetRowEnumerator();
  40. // 少于2行验证
  41. if (sheet.LastRowNum < 2)
  42. {
  43. //"第一行应为填写说明,第二行应为标题行,请勿修改模板结构。"
  44. return;
  45. }
  46. // 读取表头
  47. rows.MoveNext();
  48. IRow headerRow = (IRow)rows.Current;
  49. int index = 0;
  50. int SAMPLE_TYPE_INDEX = index++;
  51. int SCHOOL_ID_INDEX = index++;
  52. int SCHOOL_NAME_INDEX = index++;
  53. int SCHOOL_BID_INDEX = index++;
  54. int SCHOOL_BNAME_INDEX = index++;
  55. int NAME_INDEX = index++;
  56. int CERT_TYPE_INDEX = index++;
  57. int ID_NUM_INDEX = index++;
  58. int EXAM_NUMBER_INDEX = index++;
  59. int COURSE_COMB_INDEX = index++;
  60. int GRADE_INDEX = index++;
  61. int CLASS_INDEX = index++;
  62. int COURSE_START_INDEX = index;
  63. Dictionary<int, string> headers = new()
  64. {
  65. { SAMPLE_TYPE_INDEX, "抽样类型" },
  66. { SCHOOL_ID_INDEX, "学校ID" },
  67. { SCHOOL_NAME_INDEX, "学校" },
  68. { SCHOOL_BID_INDEX, "校区ID" },
  69. { SCHOOL_BNAME_INDEX, "校区" },
  70. { NAME_INDEX, "姓名" },
  71. { CERT_TYPE_INDEX, "证件类型" },
  72. { ID_NUM_INDEX, "证件号码" },
  73. { EXAM_NUMBER_INDEX, "考号" },
  74. { COURSE_COMB_INDEX, "选科组合" },
  75. { GRADE_INDEX, "年级" },
  76. { CLASS_INDEX, "班级" },
  77. };
  78. List<string> headerErrors = new();
  79. for (int i = 0; i < COURSE_START_INDEX; i++)
  80. {
  81. if (headerRow.GetCell(i)?.ToString() != headers[i])
  82. {
  83. char letter = (char)('A' + i);
  84. headerErrors.Add(letter.ToString());
  85. }
  86. }
  87. if (headerErrors.Any())
  88. {
  89. string columnErrors = string.Join("、", headerErrors);
  90. //result.ErrorMessage.Add($"第1行标题行{columnErrors}列名错误。从A列开始依次应为抽样类型、学校ID、学校、姓名、证件类型、证件号码、考号、年级、班级。");
  91. //return result;
  92. return;
  93. }
  94. //result.StructureCorrect = true;
  95. #endregion
  96. #region 2.读取数据
  97. var examPlan = await _rep.Change<ExamPlan>().DetachedEntities.FirstOrDefaultAsync(t => t.Id == examPlanId) ?? throw Oops.Oh(ErrorCode.E2001, "监测计划");
  98. var examGradeDict = (await _examGradeService.GetListByExamPlanId(examPlanId)).ToDictionary(t => t.Grade.GradeNumber);
  99. var courseDict = (await _rep.Change<Course>().DetachedEntities.ToListAsync()).ToDictionary(t => t.Name);
  100. // 获取需要导入的科目列表
  101. Dictionary<int, Course> courses = new();
  102. for (int gi = COURSE_START_INDEX; gi < headerRow.LastCellNum; gi++)
  103. {
  104. var cn = headerRow.GetCell(gi).ToString() ?? "";
  105. if (string.IsNullOrEmpty(cn))
  106. {
  107. continue;
  108. }
  109. courses.Add(gi, courseDict[cn]);
  110. }
  111. int rn = 1;
  112. List<ExamScoreImportDto> sourceItems = new();
  113. while (rows.MoveNext())
  114. {
  115. rn++;
  116. IRow row = (IRow)rows.Current;
  117. string rv = row.GetCell(0)?.ToString().Trim() ?? "";
  118. if (rv == "")
  119. {
  120. break;
  121. }
  122. for (int gi = COURSE_START_INDEX; gi < headerRow.LastCellNum; gi++)
  123. {
  124. var course = courses[gi];
  125. var sbid = row.GetCell(SCHOOL_BID_INDEX)?.NumericCellValue;
  126. var ccid = row.GetCell(COURSE_COMB_INDEX)?.NumericCellValue;
  127. ExamScoreImportDto item = new()
  128. {
  129. RowNumber = rn,
  130. SampleType = (ExamSampleType)row.GetCell(SAMPLE_TYPE_INDEX).NumericCellValue,
  131. SysOrgId = (short)row.GetCell(SCHOOL_ID_INDEX).NumericCellValue,
  132. SysOrgBranchId = sbid > 0 ? (short)sbid : null,
  133. StudentName = (row.GetCell(NAME_INDEX)?.ToString() ?? "").ClearWhitespace(),
  134. CertificateType = (CertificateType)row.GetCell(CERT_TYPE_INDEX).NumericCellValue,
  135. IdNumber = (row.GetCell(ID_NUM_INDEX)?.ToString() ?? "").ClearWhitespace(),
  136. ExamNumber = row.GetCell(EXAM_NUMBER_INDEX)?.ToString() ?? "",
  137. GradeNumber = (short)row.GetCell(GRADE_INDEX).NumericCellValue,
  138. ClassNumber = (short)row.GetCell(CLASS_INDEX).NumericCellValue,
  139. CourseId = course.Id,
  140. NceeCourseCombId = ccid > 0 ? (short)ccid : null,
  141. };
  142. string scoreStr = row.GetCell(gi)?.ToString() ?? "";
  143. if (decimal.TryParse(scoreStr, out decimal score))
  144. {
  145. item.Score = score;
  146. }
  147. else if (scoreStr.Contains('特') || scoreStr.Contains('缺'))
  148. {
  149. item.IsExcluded = true;
  150. }
  151. if (item.IdNumber.Length == 18)
  152. {
  153. if (int.TryParse(item.IdNumber.AsSpan(16, 1), out int gn))
  154. {
  155. item.Gender = (short)(gn % 2 == 0 ? 2 : 1);
  156. }
  157. }
  158. var eg = examGradeDict[item.GradeNumber];
  159. item.ExamGradeId = eg.Id;
  160. item.GradeId = eg.GradeId;
  161. item.GradeBeginYear = eg.GradeBeginYear;
  162. item.GradeEndYear = eg.GradeEndYear;
  163. item.SchoolClassId = _schoolClassService.GetSchoolClassId(examPlan.EducationStage, item.SysOrgId, eg.GradeBeginYear, item.ClassNumber);
  164. sourceItems.Add(item);
  165. }
  166. }
  167. workbook.Close();
  168. fs.Close();
  169. #endregion
  170. #region 3.处理班级
  171. var scs = sourceItems.GroupBy(t => t.SchoolClassId).Select(t => new BatchInsertSchoolClassInput
  172. {
  173. Id = t.Key,
  174. SysOrgId = t.FirstOrDefault().SysOrgId,
  175. SysOrgBranchId = t.FirstOrDefault().SysOrgBranchId,
  176. EducationStage = examPlan.EducationStage,
  177. EducationYears = examPlan.EducationYears,
  178. ClassNumber = t.FirstOrDefault().ClassNumber,
  179. GradeBeginYear = t.FirstOrDefault().GradeBeginYear,
  180. GradeEndYear = t.FirstOrDefault().GradeEndYear,
  181. }).Distinct().ToList();
  182. // 插入没有的班级
  183. await _schoolClassService.BatchInsert(scs);
  184. #endregion
  185. #region 4.处理学生
  186. // 清理学生
  187. string deleteStudentSql = $"DELETE FROM exam_student WHERE exam_plan_id = {examPlanId};";
  188. await _rep.SqlNonQueryAsync(deleteStudentSql);
  189. var uid = CurrentSysUserInfo.SysUserId;
  190. if (uid == 0)
  191. {
  192. uid = 1;
  193. }
  194. var stus = sourceItems.GroupBy(t => t.RowNumber).Select(t => t.FirstOrDefault()).ToList();
  195. List<string> insertValues = new();
  196. int scount = stus.Count;
  197. for (int i = 0; i < scount; i++)
  198. {
  199. var t = stus[i];
  200. string sobid = "NULL";
  201. if (t.SysOrgBranchId.HasValue)
  202. {
  203. sobid = t.SysOrgBranchId.ToString();
  204. }
  205. insertValues.Add($"({examPlanId}, {t.SysOrgId}, {sobid}, {t.ExamGradeId}, {t.GradeId}, {t.SchoolClassId}, {t.ClassNumber}, '{t.ExamNumber}', '{t.StudentName}', {(short)t.CertificateType}, '{t.IdNumber}', {t.Gender}, {uid}, CURRENT_TIMESTAMP, 0)");
  206. if ((i + 1) % 1000 == 0 || i == scount - 1)
  207. {
  208. string insertSql = $@"
  209. INSERT INTO exam_student(exam_plan_id, sys_org_id, sys_org_branch_id, exam_grade_id, grade_id, school_class_id, class_number, exam_number, `name`, certificate_type, id_number, gender, create_sys_user_id, create_time, is_deleted) VALUES
  210. {string.Join(",", insertValues)}
  211. ";
  212. await _rep.SqlNonQueryAsync(insertSql);
  213. insertValues.Clear();
  214. }
  215. }
  216. #endregion
  217. #region 5.批量导入
  218. // 清理成绩
  219. string deleteScoreSql = $"DELETE FROM exam_score WHERE exam_plan_id = {examPlanId};";
  220. await _rep.SqlNonQueryAsync(deleteScoreSql);
  221. List<string> selects = new();
  222. scount = sourceItems.Count;
  223. for (int i = 0; i < scount; i++)
  224. {
  225. var t = sourceItems[i];
  226. string sobid = "NULL";
  227. if (t.SysOrgBranchId.HasValue)
  228. {
  229. sobid = t.SysOrgBranchId.ToString();
  230. }
  231. string ccid = "NULL";
  232. if (t.NceeCourseCombId.HasValue)
  233. {
  234. ccid = t.NceeCourseCombId.ToString();
  235. }
  236. var ied = t.IsExcluded ? 1 : 0;
  237. selects.Add($@"
  238. SELECT {(short)t.SampleType} est, {t.SysOrgId} soid, {sobid} as sobid, {t.GradeId} gid, {examPlan.SemesterId} smid, {t.SchoolClassId} scid, {t.ClassNumber} cn, '{t.ExamNumber}' en, {ccid} ccid, {t.CourseId} cid, {t.Score} s, {ied} ied ");
  239. if ((i + 1) % 500 == 0 || i == scount - 1)
  240. {
  241. string insertSql = $@"
  242. INSERT INTO exam_score(exam_plan_id, education_stage, exam_sample_type, sys_org_id, sys_org_branch_id, grade_id, semester_id, school_class_id, class_number, exam_student_id, ncee_course_comb_id, course_id, score, is_excluded)
  243. SELECT {examPlanId}, {(short)examPlan.EducationStage}, T1.est, T1.soid, T1.sobid, T1.gid, T1.smid, T1.scid, T1.cn, T2.id, T1.ccid, T1.cid, T1.s, T1.ied
  244. FROM
  245. ({string.Join("UNION ALL", selects)}
  246. ) AS T1
  247. JOIN (SELECT id, school_class_id, exam_number FROM exam_student WHERE exam_plan_id = {examPlanId}) AS T2
  248. ON T1.scid = T2.school_class_id AND T1.en = T2.exam_number
  249. ";
  250. await _rep.SqlNonQueryAsync(insertSql);
  251. selects.Clear();
  252. }
  253. }
  254. #endregion
  255. }
  256. catch (Exception ex)
  257. {
  258. throw new Exception(ex.Message);
  259. }
  260. finally
  261. {
  262. File.Delete(filePath);
  263. }
  264. }
  265. /// <summary>
  266. /// 批量导入学生总成绩
  267. /// </summary>
  268. /// <param name="filePath"></param>
  269. /// <param name="examPlanId"></param>
  270. /// <returns></returns>
  271. public async Task UploadImportStudentTotalScore(string filePath, int examPlanId)
  272. {
  273. /* ----------------------------------------------------------
  274. * 处理步骤:
  275. * 1.验证表头
  276. * 2.读取数据
  277. * 3.处理班级
  278. * 4.批量插入
  279. ---------------------------------------------------------- */
  280. try
  281. {
  282. #region 1.验证表结构
  283. using FileStream fs = new(filePath, FileMode.Open, FileAccess.Read);
  284. IWorkbook workbook = ExcelUtil.GetWorkbook(filePath, fs);
  285. var sheet = workbook.GetSheetAt(0);
  286. var rows = sheet.GetRowEnumerator();
  287. // 少于2行验证
  288. if (sheet.LastRowNum < 2)
  289. {
  290. //"第一行应为填写说明,第二行应为标题行,请勿修改模板结构。"
  291. return;
  292. }
  293. // 读取表头
  294. rows.MoveNext();
  295. IRow headerRow = (IRow)rows.Current;
  296. int index = 0;
  297. int SAMPLE_TYPE_INDEX = index++;
  298. int SCHOOL_ID_INDEX = index++;
  299. int SCHOOL_NAME_INDEX = index++;
  300. int SCHOOL_BID_INDEX = index++;
  301. int SCHOOL_BNAME_INDEX = index++;
  302. int NAME_INDEX = index++;
  303. int CERT_TYPE_INDEX = index++;
  304. int ID_NUM_INDEX = index++;
  305. int EXAM_NUMBER_INDEX = index++;
  306. int COURSE_COMB_INDEX = index++;
  307. int GRADE_INDEX = index++;
  308. int CLASS_INDEX = index++;
  309. int SP_INDEX = index++;
  310. int ABSENT_REPLACE_INDEX = index++;
  311. int COURSE_START_INDEX = index;
  312. Dictionary<int, string> headers = new()
  313. {
  314. { SAMPLE_TYPE_INDEX, "抽样类型" },
  315. { SCHOOL_ID_INDEX, "学校ID" },
  316. { SCHOOL_NAME_INDEX, "学校" },
  317. { SCHOOL_BID_INDEX, "校区ID" },
  318. { SCHOOL_BNAME_INDEX, "校区" },
  319. { NAME_INDEX, "姓名" },
  320. { CERT_TYPE_INDEX, "证件类型" },
  321. { ID_NUM_INDEX, "证件号码" },
  322. { EXAM_NUMBER_INDEX, "考号" },
  323. { COURSE_COMB_INDEX, "选科组合" },
  324. { GRADE_INDEX, "年级" },
  325. { CLASS_INDEX, "班级" },
  326. { SP_INDEX, "特殊学生" },
  327. { ABSENT_REPLACE_INDEX, "缺测替补" },
  328. };
  329. List<string> headerErrors = [];
  330. for (int i = 0; i < COURSE_START_INDEX; i++)
  331. {
  332. if (headerRow.GetCell(i)?.ToString() != headers[i])
  333. {
  334. char letter = (char)('A' + i);
  335. headerErrors.Add(letter.ToString());
  336. }
  337. }
  338. if (headerErrors.Count != 0)
  339. {
  340. string columnErrors = string.Join("、", headerErrors);
  341. //result.ErrorMessage.Add($"第1行标题行{columnErrors}列名错误。从A列开始依次应为抽样类型、学校ID、学校、姓名、证件类型、证件号码、考号、年级、班级。");
  342. //return result;
  343. return;
  344. }
  345. //result.StructureCorrect = true;
  346. #endregion
  347. #region 2.读取数据
  348. var examPlan = await _rep.Change<ExamPlan>().DetachedEntities.FirstOrDefaultAsync(t => t.Id == examPlanId) ?? throw Oops.Oh(ErrorCode.E2001, "监测计划");
  349. var examGradeDict = (await _examGradeService.GetListByExamPlanId(examPlanId)).ToDictionary(t => t.Grade.GradeNumber);
  350. var courseDict = (await _rep.Change<Course>().DetachedEntities.ToListAsync()).ToDictionary(t => t.Name);
  351. var examCourses = await _rep.Change<ExamCourse>().DetachedEntities.Where(t => t.ExamPlanId == examPlanId).Select(t => new { t.GradeId, t.CourseId }).ToListAsync();
  352. // 获取需要导入的科目列表
  353. Dictionary<int, Course> courses = [];
  354. for (int gi = COURSE_START_INDEX; gi < headerRow.LastCellNum; gi++)
  355. {
  356. var cn = headerRow.GetCell(gi).ToString() ?? "";
  357. if (string.IsNullOrEmpty(cn))
  358. {
  359. continue;
  360. }
  361. courses.Add(gi, courseDict[cn]);
  362. }
  363. int rn = 1;
  364. List<ExamScoreImportDto> sourceItems = [];
  365. while (rows.MoveNext())
  366. {
  367. rn++;
  368. IRow row = (IRow)rows.Current;
  369. string rv = row.GetCell(0)?.ToString().Trim() ?? "";
  370. if (rv == "")
  371. {
  372. break;
  373. }
  374. var sampleType = (ExamSampleType)row.GetCell(SAMPLE_TYPE_INDEX).NumericCellValue;
  375. var sysOrgId = (short)row.GetCell(SCHOOL_ID_INDEX).NumericCellValue;
  376. var gradeNumber = (short)row.GetCell(GRADE_INDEX).NumericCellValue;
  377. var classNumber = (short)row.GetCell(CLASS_INDEX).NumericCellValue;
  378. var spStr = row.GetCell(SP_INDEX)?.ToString()?.Trim() ?? "";
  379. var isSp = spStr.Contains("特殊");
  380. var absentStr = row.GetCell(ABSENT_REPLACE_INDEX)?.ToString()?.Trim() ?? "";
  381. string remark = isSp ? spStr : "";
  382. if (absentStr.Length > 0)
  383. {
  384. if (remark.Length == 0)
  385. {
  386. remark = absentStr;
  387. }
  388. else
  389. {
  390. remark = $"{remark},{absentStr}";
  391. }
  392. }
  393. for (int gi = COURSE_START_INDEX; gi < headerRow.LastCellNum; gi++)
  394. {
  395. var course = courses[gi];
  396. var eg = examGradeDict[gradeNumber];
  397. // 跳过未监测科目
  398. if (!examCourses.Any(t => t.GradeId == eg.GradeId && t.CourseId == course.Id))
  399. {
  400. continue;
  401. }
  402. var sbid = row.GetCell(SCHOOL_BID_INDEX)?.NumericCellValue;
  403. var ccid = row.GetCell(COURSE_COMB_INDEX)?.NumericCellValue;
  404. ExamScoreImportDto item = new()
  405. {
  406. RowNumber = rn,
  407. SampleType = sampleType,
  408. SysOrgId = sysOrgId,
  409. SysOrgBranchId = sbid > 0 ? (short)sbid : null,
  410. StudentName = (row.GetCell(NAME_INDEX)?.ToString() ?? "").ClearWhitespace(),
  411. CertificateType = (CertificateType)row.GetCell(CERT_TYPE_INDEX).NumericCellValue,
  412. IdNumber = (row.GetCell(ID_NUM_INDEX)?.ToString() ?? "").ClearWhitespace(),
  413. ExamNumber = (row.GetCell(EXAM_NUMBER_INDEX)?.ToString() ?? "").ClearWhitespace(),
  414. GradeNumber = gradeNumber,
  415. ClassNumber = classNumber,
  416. CourseId = course.Id,
  417. NceeCourseCombId = ccid > 0 ? (short)ccid : null,
  418. Remark = remark,
  419. };
  420. string scoreStr = row.GetCell(gi)?.ToString() ?? "";
  421. if (decimal.TryParse(scoreStr, out decimal score))
  422. {
  423. item.Score = score;
  424. }
  425. else if (scoreStr.Contains('特') || scoreStr.Contains('缺'))
  426. {
  427. item.IsExcluded = true;
  428. item.IsAbsent = scoreStr.Contains('缺');
  429. }
  430. if (isSp)
  431. {
  432. item.IsSpecial = true;
  433. item.IsExcluded = true;
  434. }
  435. if (item.IdNumber.Length == 18)
  436. {
  437. if (int.TryParse(item.IdNumber.AsSpan(16, 1), out int gn))
  438. {
  439. item.Gender = (short)(gn % 2 == 0 ? 2 : 1);
  440. }
  441. }
  442. item.ExamGradeId = eg.Id;
  443. item.GradeId = eg.GradeId;
  444. item.GradeBeginYear = eg.GradeBeginYear;
  445. item.GradeEndYear = eg.GradeEndYear;
  446. item.SchoolClassId = _schoolClassService.GetSchoolClassId(examPlan.EducationStage, item.SysOrgId, eg.GradeBeginYear, item.ClassNumber);
  447. sourceItems.Add(item);
  448. }
  449. }
  450. workbook.Close();
  451. fs.Close();
  452. #endregion
  453. #region 3.处理班级
  454. var scs = sourceItems.GroupBy(t => t.SchoolClassId).Select(t => new BatchInsertSchoolClassInput
  455. {
  456. Id = t.Key,
  457. SysOrgId = t.FirstOrDefault().SysOrgId,
  458. SysOrgBranchId = t.FirstOrDefault().SysOrgBranchId,
  459. EducationStage = examPlan.EducationStage,
  460. EducationYears = examPlan.EducationYears,
  461. ClassNumber = t.FirstOrDefault().ClassNumber,
  462. GradeBeginYear = t.FirstOrDefault().GradeBeginYear,
  463. GradeEndYear = t.FirstOrDefault().GradeEndYear,
  464. }).Distinct().ToList();
  465. // 插入没有的班级
  466. await _schoolClassService.BatchInsert(scs);
  467. #endregion
  468. #region 4.批量导入
  469. // 清理成绩
  470. string deleteScoreSql = $"DELETE FROM exam_score WHERE exam_plan_id = {examPlanId};";
  471. await _rep.SqlNonQueryAsync(deleteScoreSql);
  472. List<string> selects = [];
  473. var scount = sourceItems.Count;
  474. for (int i = 0; i < scount; i++)
  475. {
  476. var t = sourceItems[i];
  477. string sobid = "NULL";
  478. if (t.SysOrgBranchId.HasValue)
  479. {
  480. sobid = t.SysOrgBranchId.ToString();
  481. }
  482. string ccid = "NULL";
  483. if (t.NceeCourseCombId.HasValue)
  484. {
  485. ccid = t.NceeCourseCombId.ToString();
  486. }
  487. var ied = t.IsExcluded ? 1 : 0;
  488. var isp = t.IsSpecial ? 1 : 0;
  489. var isa = t.IsAbsent ? 1 : 0;
  490. selects.Add($@"
  491. SELECT {(short)t.SampleType} est, {t.SysOrgId} soid, {sobid} as sobid, {t.GradeId} gid, {examPlan.SemesterId} smid, {t.SchoolClassId} scid, {t.ClassNumber} cn, '{t.ExamNumber}' en, {ccid} ccid, {t.CourseId} cid, {t.Score} s, {ied} ied, {isp} isp, {isa} isa, '{t.Remark}' r ");
  492. if ((i + 1) % 500 == 0 || i == scount - 1)
  493. {
  494. string insertSql = $@"
  495. SET @examSampleId = (SELECT MAX(id) FROM exam_sample WHERE exam_plan_id = {examPlanId} AND is_selected = 1);
  496. INSERT INTO exam_score(exam_plan_id, education_stage, exam_sample_type, sys_org_id, sys_org_branch_id, grade_id, semester_id, school_class_id, class_number, exam_student_id, ncee_course_comb_id, course_id, score, is_excluded, is_special, is_absent, remark, exam_number)
  497. SELECT {examPlanId}, {(short)examPlan.EducationStage}, T1.est, T1.soid, T1.sobid, T1.gid, T1.smid, T1.scid, T1.cn, T2.id, T1.ccid, T1.cid, T1.s, T1.ied, T1.isp, T1.isa, T1.r, T1.en
  498. FROM
  499. ({string.Join("UNION ALL", selects)}
  500. ) AS T1
  501. JOIN (SELECT exam_number, exam_student_id AS id FROM exam_sample_student WHERE exam_sample_id = @examSampleId) AS T2
  502. ON T1.en = T2.exam_number;
  503. ";
  504. //await _rep.SqlNonQueryAsync(insertSql);
  505. await insertSql.SetCommandTimeout(60000).SqlNonQueryAsync();
  506. selects.Clear();
  507. }
  508. }
  509. #endregion
  510. #region 5.计算总分和分段
  511. await $@"
  512. -- 更新各科成绩分段
  513. UPDATE exam_score AS T1
  514. JOIN
  515. (
  516. SELECT T1.id, T1.grade_id, T1.course_id, get_exam_score_range_id(T2.exam_score_range_type, T1.score) AS exam_score_range_id
  517. FROM exam_score AS T1
  518. JOIN exam_course AS T2 ON T1.exam_plan_id = T2.exam_plan_id AND T1.grade_id = T2.grade_id AND T1.course_id = T2.course_id
  519. WHERE T1.exam_plan_id = {examPlanId}
  520. ) AS T2 ON T1.id = T2.id
  521. SET T1.exam_score_range_id = T2.exam_score_range_id
  522. WHERE T1.exam_plan_id = {examPlanId};
  523. -- 更新总成绩和分段
  524. DELETE FROM exam_score_total WHERE exam_plan_id = {examPlanId};
  525. INSERT INTO exam_score_total
  526. (
  527. exam_plan_id,
  528. education_stage,
  529. exam_sample_type,
  530. sys_org_id,
  531. sys_org_branch_id,
  532. grade_id, semester_id,
  533. school_class_id,
  534. class_number,
  535. exam_student_id,
  536. ncee_course_comb_id,
  537. course_count,
  538. score,
  539. remark,
  540. exam_number,
  541. is_special,
  542. absent_count,
  543. is_excluded,
  544. exam_score_range_id
  545. )
  546. SELECT
  547. {examPlanId},
  548. T1.education_stage,
  549. T1.exam_sample_type,
  550. T1.sys_org_id,
  551. T1.sys_org_branch_id,
  552. T1.grade_id,
  553. T1.semester_id,
  554. T1.school_class_id,
  555. T1.class_number,
  556. T1.exam_student_id,
  557. T1.ncee_course_comb_id,
  558. T1.course_count,
  559. T1.score,
  560. T1.remark,
  561. T1.exam_number,
  562. T1.is_special,
  563. T1.absent_count,
  564. CASE WHEN T1.is_excluded > 0 THEN 1 ELSE 0 END AS is_excluded,
  565. get_exam_score_range_id(T2.exam_score_range_type, T1.score) AS exam_score_range_id
  566. FROM
  567. (
  568. SELECT
  569. education_stage,
  570. exam_sample_type,
  571. sys_org_id,
  572. sys_org_branch_id,
  573. grade_id, semester_id,
  574. school_class_id,
  575. class_number,
  576. exam_student_id,
  577. ncee_course_comb_id,
  578. COUNT(1) AS course_count,
  579. SUM(IFNULL(score, 0)) AS score,
  580. SUM(IFNULL(is_excluded, 0)) AS is_excluded,
  581. MAX(remark) AS remark,
  582. MAX(exam_number) AS exam_number,
  583. MAX(is_special) AS is_special,
  584. SUM(is_absent) AS absent_count
  585. FROM exam_score
  586. WHERE exam_plan_id = {examPlanId}
  587. GROUP BY education_stage, exam_sample_type, sys_org_id, sys_org_branch_id, grade_id, semester_id, school_class_id, class_number, exam_student_id, ncee_course_comb_id
  588. ) AS T1
  589. JOIN
  590. (
  591. SELECT grade_id, exam_score_range_type FROM exam_grade WHERE exam_plan_id = {examPlanId}
  592. ) AS T2 ON T1.grade_id = T2.grade_id;
  593. ".SetCommandTimeout(600000).SqlNonQueryAsync();
  594. #endregion
  595. }
  596. catch (Exception ex)
  597. {
  598. throw new Exception(ex.Message);
  599. }
  600. finally
  601. {
  602. File.Delete(filePath);
  603. }
  604. }
  605. /// <summary>
  606. /// 上传并合并小题成绩导出
  607. /// </summary>
  608. /// <param name="filePath"></param>
  609. /// <param name="examPlanId"></param>
  610. /// <returns></returns>
  611. /// <exception cref="Exception"></exception>
  612. public async Task<(string, byte[])> UploadImportStudentMinorScore(string filePath, int examPlanId)
  613. {
  614. var examPlan = await _rep.Change<ExamPlan>().DetachedEntities.FirstOrDefaultAsync(t => t.Id == examPlanId) ?? throw Oops.Oh(ErrorCode.E2001, "监测计划");
  615. // 解压输出目录
  616. string unzipPath = Path.Combine(FileUtil.GetTempFileRoot(), Path.GetFileNameWithoutExtension(filePath));
  617. Directory.CreateDirectory(unzipPath);
  618. // 临时存放目录
  619. string outputRoot = Path.Combine(FileUtil.GetTempFileRoot(), $"{Guid.NewGuid()}");
  620. Directory.CreateDirectory(outputRoot);
  621. string outputFilePath = Path.Combine(outputRoot, $"{examPlan.Name}-区校合并-小题成绩");
  622. Directory.CreateDirectory(outputFilePath);
  623. try
  624. {
  625. List<ExamScoreMinorFileInfo> fileInfos = [];
  626. var gradeDict = (await _rep.Change<Grade>().DetachedEntities.Where(t => t.EducationStage == examPlan.EducationStage).ToListAsync()).ToDictionary(t => t.Name);
  627. var courseDict = (await _rep.Change<Course>().DetachedEntities.ToListAsync()).ToDictionary(t => t.Name);
  628. #region 1.读取文件信息
  629. ZipFile.ExtractToDirectory(filePath, unzipPath, System.Text.Encoding.GetEncoding("GBK"), true);
  630. var sampleTypePaths = Directory.GetDirectories(unzipPath);
  631. foreach (var stpath in sampleTypePaths)
  632. {
  633. var tn = Path.GetFileNameWithoutExtension(stpath).Split("-");
  634. ExamSampleType est;
  635. if (int.TryParse(tn[0], out int estInt))
  636. {
  637. est = (ExamSampleType)estInt;
  638. }
  639. else
  640. {
  641. throw new ArgumentException("监测抽样类型错误");
  642. }
  643. var scoreFiles = Directory.GetFiles(stpath).Where(t => t.EndsWith(".xlsx") || t.EndsWith(".xls")).Where(t => !t.StartsWith("~"));
  644. foreach (var scoreFile in scoreFiles)
  645. {
  646. var fn = Path.GetFileNameWithoutExtension(scoreFile).Split("-");
  647. var gradeName = fn[1];
  648. var grade = gradeDict[gradeName];
  649. var courseName = fn[2];
  650. var course = courseDict[courseName];
  651. fileInfos.Add(new()
  652. {
  653. ExamSampleType = est,
  654. FilePath = scoreFile,
  655. GradeId = grade.Id,
  656. Grade = grade,
  657. CourseId = course.Id,
  658. Course = course,
  659. });
  660. }
  661. }
  662. #endregion
  663. #region 2.合并文件
  664. var cfiles = fileInfos.Where(t => t.ExamSampleType == ExamSampleType.DISTRICT);
  665. foreach (var cfile in cfiles)
  666. {
  667. var qminors = await _rep.Change<ExamPaperQuestionMinor>().DetachedEntities.Where(t => t.ExamPaper.ExamPlanId == examPlanId && t.ExamPaper.CourseId == cfile.CourseId && t.ExamPaper.GradeId == cfile.GradeId && t.IsLeaf == true).ToListAsync();
  668. var examSampleStudents = await _rep.Change<ExamSampleStudent>().DetachedEntities
  669. .Where(t => t.ExamSample.ExamPlanId == examPlanId && t.ExamSample.IsSelected == true && t.ExamStudent.GradeId == cfile.GradeId)
  670. .Select(t => new { t.ExamNumber, t.ExamStudentId, t.ExamStudent.SysOrgId })
  671. .ToListAsync();
  672. var sfile = fileInfos.FirstOrDefault(t => t.ExamSampleType == ExamSampleType.SCHOOL_EXAM && t.GradeId == cfile.GradeId && t.CourseId == cfile.CourseId) ?? throw new ArgumentNullException($"{cfile}对应校考小题成绩未找到");
  673. // 区文件
  674. using FileStream c_fs = new(cfile.FilePath, FileMode.Open, FileAccess.Read);
  675. IWorkbook c_workbook = ExcelUtil.GetWorkbook(filePath, c_fs);
  676. var c_sheet = c_workbook.GetSheetAt(0);
  677. var c_rows = c_sheet.GetRowEnumerator();
  678. // 输出文件
  679. XSSFWorkbook o_workbook = new();
  680. ISheet o_sheet = o_workbook.CreateSheet();
  681. var o_cellStyles = _exportExcelService.GetCellStyle(o_workbook);
  682. int o_rowNum = 0;
  683. IRow o_headerRow = o_sheet.CreateRow(o_rowNum++);
  684. // 标题行
  685. Dictionary<int, ExamPaperQuestionMinor> colMinor = [];
  686. c_rows.MoveNext();
  687. var c_headerRow = (IRow)c_rows.Current;
  688. int colNum = c_headerRow.LastCellNum;
  689. for (int ci = 0; ci < colNum; ci++)
  690. {
  691. string colName = c_headerRow.GetCell(ci)?.ToString() ?? "";
  692. _exportExcelService.AddCell(c_headerRow.GetCell(ci)?.ToString(), o_headerRow, ci, o_cellStyles.CenterCellStyle, o_sheet, width: ci == 1 ? 12 : ci == 2 ? 30 : null);
  693. var qm = qminors.FirstOrDefault(t => t.ColumnName == colName);
  694. if (qm != null)
  695. {
  696. colMinor.Add(ci, qm);
  697. }
  698. }
  699. // 所有小题成绩列表
  700. List<ExamScoreMinorImportDto> items = [];
  701. // 区文件写入
  702. while (c_rows.MoveNext())
  703. {
  704. var row = (IRow)c_rows.Current;
  705. var c0 = row.GetCell(0);
  706. if (c0 == null || c0.ToString() == "")
  707. {
  708. break;
  709. }
  710. ExamScoreMinorImportDto item = new();
  711. IRow o_row = o_sheet.CreateRow(o_rowNum++);
  712. for (int tci = 0; tci < colNum; tci++)
  713. {
  714. var cell = row.GetCell(tci);
  715. if (cell == null)
  716. {
  717. continue;
  718. }
  719. if (cell.CellType == CellType.Numeric)
  720. {
  721. _exportExcelService.AddCell(cell.NumericCellValue, o_row, tci, o_cellStyles.CenterCellStyle, o_sheet);
  722. }
  723. else
  724. {
  725. _exportExcelService.AddCell(cell.ToString(), o_row, tci, o_cellStyles.CenterCellStyle, o_sheet);
  726. }
  727. if (tci == 1)
  728. {
  729. var examNumber = cell.ToString();
  730. var stu = examSampleStudents.FirstOrDefault(t => t.ExamNumber == examNumber);
  731. if (stu != null)
  732. {
  733. item.ExamStudentId = stu.ExamStudentId;
  734. item.SysOrgId = stu.SysOrgId;
  735. }
  736. }
  737. else if (tci >= 7 && colMinor.TryGetValue(tci, out ExamPaperQuestionMinor cm))
  738. {
  739. if (cell.CellType == CellType.Numeric)
  740. {
  741. item.Score = (decimal)cell.NumericCellValue;
  742. }
  743. else
  744. {
  745. if (decimal.TryParse(cell.ToString(), out decimal s))
  746. {
  747. item.Score = s;
  748. }
  749. }
  750. item.StarScore = cm.Score == 0 ? 0 : item.Score / cm.Score;
  751. if (item.ExamStudentId != 0)
  752. {
  753. items.Add(new()
  754. {
  755. ExamStudentId = item.ExamStudentId,
  756. SysOrgId = item.SysOrgId,
  757. ExamPaperQuestionMinorId = cm.Id,
  758. Score = item.Score,
  759. StarScore = item.StarScore
  760. });
  761. }
  762. }
  763. }
  764. }
  765. // 校文件
  766. using FileStream s_fs = new(sfile.FilePath, FileMode.Open, FileAccess.Read);
  767. IWorkbook s_workbook = ExcelUtil.GetWorkbook(sfile.FilePath, s_fs);
  768. var s_sheet = s_workbook.GetSheetAt(0);
  769. var s_rows = s_sheet.GetRowEnumerator();
  770. s_rows.MoveNext();
  771. // 校文件写入
  772. while (s_rows.MoveNext())
  773. {
  774. var row = (IRow)s_rows.Current;
  775. var c0 = row.GetCell(0);
  776. if (c0 == null || c0.ToString() == "")
  777. {
  778. break;
  779. }
  780. ExamScoreMinorImportDto item = new();
  781. IRow o_row = o_sheet.CreateRow(o_rowNum++);
  782. for (int tci = 0; tci < colNum; tci++)
  783. {
  784. var cell = row.GetCell(tci);
  785. if (cell == null)
  786. {
  787. continue;
  788. }
  789. if (cell.CellType == CellType.Numeric)
  790. {
  791. _exportExcelService.AddCell(cell.NumericCellValue, o_row, tci, o_cellStyles.CenterCellStyle, o_sheet);
  792. }
  793. else
  794. {
  795. _exportExcelService.AddCell(cell.ToString(), o_row, tci, o_cellStyles.CenterCellStyle, o_sheet);
  796. }
  797. if (tci == 1)
  798. {
  799. var examNumber = cell.ToString();
  800. var stu = examSampleStudents.FirstOrDefault(t => t.ExamNumber == examNumber);
  801. if (stu != null)
  802. {
  803. item.ExamStudentId = stu.ExamStudentId;
  804. item.SysOrgId = stu.SysOrgId;
  805. }
  806. }
  807. else if (tci >= 7 && colMinor.TryGetValue(tci, out ExamPaperQuestionMinor cm))
  808. {
  809. if (cell.CellType == CellType.Numeric)
  810. {
  811. item.Score = (decimal)cell.NumericCellValue;
  812. }
  813. else
  814. {
  815. if (decimal.TryParse(cell.ToString(), out decimal s))
  816. {
  817. item.Score = s;
  818. }
  819. }
  820. item.StarScore = cm.Score == 0 ? 0 : item.Score / cm.Score;
  821. if (item.ExamStudentId != 0)
  822. {
  823. items.Add(new()
  824. {
  825. ExamStudentId = item.ExamStudentId,
  826. SysOrgId = item.SysOrgId,
  827. ExamPaperQuestionMinorId = cm.Id,
  828. Score = item.Score,
  829. StarScore = item.StarScore
  830. });
  831. }
  832. }
  833. }
  834. }
  835. o_sheet.CreateFreezePane(0, 1);
  836. MemoryStream o_ms = new();
  837. o_workbook.Write(o_ms, false);
  838. o_ms.Flush();
  839. await File.WriteAllBytesAsync(Path.Combine(outputFilePath, $"{examPlan.EducationStage.GetDescription()}-{cfile.Grade.Name}-{cfile.Course.Name}-小题成绩.xlsx"), o_ms.ToArray());
  840. #region 批量导入
  841. // 清理成绩
  842. string deleteScoreSql = $"DELETE FROM exam_score_minor WHERE exam_plan_id = {examPlanId} AND grade_id = {cfile.GradeId} AND course_id = {cfile.CourseId};";
  843. //await _rep.SqlNonQueryAsync(deleteScoreSql);
  844. await deleteScoreSql.SetCommandTimeout(60000).SqlNonQueryAsync();
  845. List<string> values = [];
  846. var scount = items.Count;
  847. for (int i = 0; i < scount; i++)
  848. {
  849. var item = items[i];
  850. values.Add($"({examPlanId},{item.SysOrgId},{item.ExamStudentId},{cfile.GradeId},{cfile.CourseId},{item.ExamPaperQuestionMinorId},{item.Score},{item.StarScore})");
  851. if ((i + 1) % 2000 == 0 || i == scount - 1)
  852. {
  853. string insertSql = $"INSERT INTO exam_score_minor(exam_plan_id,sys_org_id,exam_student_id,grade_id,course_id,exam_paper_question_minor_id,score,star_score) VALUES {string.Join(",", values)}";
  854. //await _rep.SqlNonQueryAsync(insertSql);
  855. await insertSql.SetCommandTimeout(60000).SqlNonQueryAsync();
  856. values.Clear();
  857. }
  858. }
  859. #endregion
  860. }
  861. #endregion
  862. string outFileName = $"{examPlan.Name}-区校合并-小题成绩.zip";
  863. string oFilePath = Path.Combine(outputRoot, outFileName);
  864. ICSharpCode.SharpZipLib.Zip.FastZip zip = new();
  865. zip.CreateZip(oFilePath, outputFilePath, true, string.Empty);
  866. var retBytes = await File.ReadAllBytesAsync(oFilePath);
  867. return (outFileName, retBytes);
  868. }
  869. catch (Exception ex)
  870. {
  871. throw new Exception(ex.Message);
  872. }
  873. finally
  874. {
  875. File.Delete(filePath);
  876. Directory.Delete(unzipPath, true);
  877. Directory.Delete(outputRoot, true);
  878. }
  879. }
  880. }