ExamScoreImportService.cs 41 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950
  1. using Furion.DatabaseAccessor.Extensions;
  2. using NPOI.SS.UserModel;
  3. using NPOI.XSSF.UserModel;
  4. using System.IO.Compression;
  5. using YBEE.EQM.Core;
  6. namespace YBEE.EQM.Application;
  7. /// <summary>
  8. /// 学生成绩导入服务
  9. /// </summary>
  10. public class ExamScoreImportService(IRepository<ExamScore> rep, ISchoolClassService schoolClassService, IExamGradeService examGradeService, IExportExcelService exportExcelService) : IExamScoreImportService, ITransient
  11. {
  12. private readonly IRepository<ExamScore> _rep = rep;
  13. private readonly ISchoolClassService _schoolClassService = schoolClassService;
  14. private readonly IExamGradeService _examGradeService = examGradeService;
  15. private readonly IExportExcelService _exportExcelService = exportExcelService;
  16. /// <summary>
  17. /// 批量导入前期未上报学生名单的各科成绩(初始化)
  18. /// </summary>
  19. /// <param name="filePath"></param>
  20. /// <param name="examPlanId"></param>
  21. /// <returns></returns>
  22. public async Task UploadImportWithoutStudentTotalScore(string filePath, int examPlanId)
  23. {
  24. /* ----------------------------------------------------------
  25. * 处理步骤:
  26. * 1.验证表头
  27. * 2.读取数据
  28. * 3.处理班级
  29. * 4.处理学生
  30. * 5.批量插入
  31. ---------------------------------------------------------- */
  32. try
  33. {
  34. #region 1.验证表结构
  35. using FileStream fs = new(filePath, FileMode.Open, FileAccess.Read);
  36. IWorkbook workbook = ExcelUtil.GetWorkbook(filePath, fs);
  37. var sheet = workbook.GetSheetAt(0);
  38. var rows = sheet.GetRowEnumerator();
  39. // 少于2行验证
  40. if (sheet.LastRowNum < 2)
  41. {
  42. //"第一行应为填写说明,第二行应为标题行,请勿修改模板结构。"
  43. return;
  44. }
  45. // 读取表头
  46. rows.MoveNext();
  47. IRow headerRow = (IRow)rows.Current;
  48. int index = 0;
  49. int SAMPLE_TYPE_INDEX = index++;
  50. int SCHOOL_ID_INDEX = index++;
  51. int SCHOOL_NAME_INDEX = index++;
  52. int SCHOOL_BID_INDEX = index++;
  53. int SCHOOL_BNAME_INDEX = index++;
  54. int NAME_INDEX = index++;
  55. int CERT_TYPE_INDEX = index++;
  56. int ID_NUM_INDEX = index++;
  57. int EXAM_NUMBER_INDEX = index++;
  58. int COURSE_COMB_INDEX = index++;
  59. int GRADE_INDEX = index++;
  60. int CLASS_INDEX = index++;
  61. int COURSE_START_INDEX = index;
  62. Dictionary<int, string> headers = new()
  63. {
  64. { SAMPLE_TYPE_INDEX, "抽样类型" },
  65. { SCHOOL_ID_INDEX, "学校ID" },
  66. { SCHOOL_NAME_INDEX, "学校" },
  67. { SCHOOL_BID_INDEX, "校区ID" },
  68. { SCHOOL_BNAME_INDEX, "校区" },
  69. { NAME_INDEX, "姓名" },
  70. { CERT_TYPE_INDEX, "证件类型" },
  71. { ID_NUM_INDEX, "证件号码" },
  72. { EXAM_NUMBER_INDEX, "考号" },
  73. { COURSE_COMB_INDEX, "选科组合" },
  74. { GRADE_INDEX, "年级" },
  75. { CLASS_INDEX, "班级" },
  76. };
  77. List<string> headerErrors = new();
  78. for (int i = 0; i < COURSE_START_INDEX; i++)
  79. {
  80. if (headerRow.GetCell(i)?.ToString() != headers[i])
  81. {
  82. char letter = (char)('A' + i);
  83. headerErrors.Add(letter.ToString());
  84. }
  85. }
  86. if (headerErrors.Any())
  87. {
  88. string columnErrors = string.Join("、", headerErrors);
  89. //result.ErrorMessage.Add($"第1行标题行{columnErrors}列名错误。从A列开始依次应为抽样类型、学校ID、学校、姓名、证件类型、证件号码、考号、年级、班级。");
  90. //return result;
  91. return;
  92. }
  93. //result.StructureCorrect = true;
  94. #endregion
  95. #region 2.读取数据
  96. var examPlan = await _rep.Change<ExamPlan>().DetachedEntities.FirstOrDefaultAsync(t => t.Id == examPlanId) ?? throw Oops.Oh(ErrorCode.E2001, "监测计划");
  97. var examGradeDict = (await _examGradeService.GetListByExamPlanId(examPlanId)).ToDictionary(t => t.Grade.GradeNumber);
  98. var courseDict = (await _rep.Change<Course>().DetachedEntities.ToListAsync()).ToDictionary(t => t.Name);
  99. // 获取需要导入的科目列表
  100. Dictionary<int, Course> courses = new();
  101. for (int gi = COURSE_START_INDEX; gi < headerRow.LastCellNum; gi++)
  102. {
  103. var cn = headerRow.GetCell(gi).ToString() ?? "";
  104. if (string.IsNullOrEmpty(cn))
  105. {
  106. continue;
  107. }
  108. courses.Add(gi, courseDict[cn]);
  109. }
  110. int rn = 1;
  111. List<ExamScoreImportDto> sourceItems = new();
  112. while (rows.MoveNext())
  113. {
  114. rn++;
  115. IRow row = (IRow)rows.Current;
  116. string rv = row.GetCell(0)?.ToString().Trim() ?? "";
  117. if (rv == "")
  118. {
  119. break;
  120. }
  121. for (int gi = COURSE_START_INDEX; gi < headerRow.LastCellNum; gi++)
  122. {
  123. var course = courses[gi];
  124. var sbid = row.GetCell(SCHOOL_BID_INDEX)?.NumericCellValue;
  125. var ccid = row.GetCell(COURSE_COMB_INDEX)?.NumericCellValue;
  126. ExamScoreImportDto item = new()
  127. {
  128. RowNumber = rn,
  129. SampleType = (ExamSampleType)row.GetCell(SAMPLE_TYPE_INDEX).NumericCellValue,
  130. SysOrgId = (short)row.GetCell(SCHOOL_ID_INDEX).NumericCellValue,
  131. SysOrgBranchId = sbid > 0 ? (short)sbid : null,
  132. StudentName = (row.GetCell(NAME_INDEX)?.ToString() ?? "").ClearWhitespace(),
  133. CertificateType = (CertificateType)row.GetCell(CERT_TYPE_INDEX).NumericCellValue,
  134. IdNumber = (row.GetCell(ID_NUM_INDEX)?.ToString() ?? "").ClearWhitespace(),
  135. ExamNumber = row.GetCell(EXAM_NUMBER_INDEX)?.ToString() ?? "",
  136. GradeNumber = (short)row.GetCell(GRADE_INDEX).NumericCellValue,
  137. ClassNumber = (short)row.GetCell(CLASS_INDEX).NumericCellValue,
  138. CourseId = course.Id,
  139. NceeCourseCombId = ccid > 0 ? (short)ccid : null,
  140. };
  141. string scoreStr = row.GetCell(gi)?.ToString() ?? "";
  142. if (decimal.TryParse(scoreStr, out decimal score))
  143. {
  144. item.Score = score;
  145. }
  146. else if (scoreStr.Contains('特') || scoreStr.Contains('缺'))
  147. {
  148. item.IsExcluded = true;
  149. }
  150. if (item.IdNumber.Length == 18)
  151. {
  152. if (int.TryParse(item.IdNumber.AsSpan(16, 1), out int gn))
  153. {
  154. item.Gender = (short)(gn % 2 == 0 ? 2 : 1);
  155. }
  156. }
  157. var eg = examGradeDict[item.GradeNumber];
  158. item.ExamGradeId = eg.Id;
  159. item.GradeId = eg.GradeId;
  160. item.GradeBeginYear = eg.GradeBeginYear;
  161. item.GradeEndYear = eg.GradeEndYear;
  162. item.SchoolClassId = _schoolClassService.GetSchoolClassId(examPlan.EducationStage, item.SysOrgId, eg.GradeBeginYear, item.ClassNumber);
  163. sourceItems.Add(item);
  164. }
  165. }
  166. workbook.Close();
  167. fs.Close();
  168. #endregion
  169. #region 3.处理班级
  170. var scs = sourceItems.GroupBy(t => t.SchoolClassId).Select(t => new BatchInsertSchoolClassInput
  171. {
  172. Id = t.Key,
  173. SysOrgId = t.FirstOrDefault().SysOrgId,
  174. SysOrgBranchId = t.FirstOrDefault().SysOrgBranchId,
  175. EducationStage = examPlan.EducationStage,
  176. EducationYears = examPlan.EducationYears,
  177. ClassNumber = t.FirstOrDefault().ClassNumber,
  178. GradeBeginYear = t.FirstOrDefault().GradeBeginYear,
  179. GradeEndYear = t.FirstOrDefault().GradeEndYear,
  180. }).Distinct().ToList();
  181. // 插入没有的班级
  182. await _schoolClassService.BatchInsert(scs);
  183. #endregion
  184. #region 4.处理学生
  185. // 清理学生
  186. string deleteStudentSql = $"DELETE FROM exam_student WHERE exam_plan_id = {examPlanId};";
  187. await _rep.SqlNonQueryAsync(deleteStudentSql);
  188. var uid = CurrentSysUserInfo.SysUserId;
  189. if (uid == 0)
  190. {
  191. uid = 1;
  192. }
  193. var stus = sourceItems.GroupBy(t => t.RowNumber).Select(t => t.FirstOrDefault()).ToList();
  194. List<string> insertValues = new();
  195. int scount = stus.Count;
  196. for (int i = 0; i < scount; i++)
  197. {
  198. var t = stus[i];
  199. string sobid = "NULL";
  200. if (t.SysOrgBranchId.HasValue)
  201. {
  202. sobid = t.SysOrgBranchId.ToString();
  203. }
  204. 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)");
  205. if ((i + 1) % 1000 == 0 || i == scount - 1)
  206. {
  207. string insertSql = $@"
  208. 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
  209. {string.Join(",", insertValues)}
  210. ";
  211. await _rep.SqlNonQueryAsync(insertSql);
  212. insertValues.Clear();
  213. }
  214. }
  215. #endregion
  216. #region 5.批量导入
  217. // 清理成绩
  218. string deleteScoreSql = $"DELETE FROM exam_score WHERE exam_plan_id = {examPlanId};";
  219. await _rep.SqlNonQueryAsync(deleteScoreSql);
  220. List<string> selects = new();
  221. scount = sourceItems.Count;
  222. for (int i = 0; i < scount; i++)
  223. {
  224. var t = sourceItems[i];
  225. string sobid = "NULL";
  226. if (t.SysOrgBranchId.HasValue)
  227. {
  228. sobid = t.SysOrgBranchId.ToString();
  229. }
  230. string ccid = "NULL";
  231. if (t.NceeCourseCombId.HasValue)
  232. {
  233. ccid = t.NceeCourseCombId.ToString();
  234. }
  235. var ied = t.IsExcluded ? 1 : 0;
  236. selects.Add($@"
  237. 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 ");
  238. if ((i + 1) % 500 == 0 || i == scount - 1)
  239. {
  240. string insertSql = $@"
  241. 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)
  242. 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
  243. FROM
  244. ({string.Join("UNION ALL", selects)}
  245. ) AS T1
  246. JOIN (SELECT id, school_class_id, exam_number FROM exam_student WHERE exam_plan_id = {examPlanId}) AS T2
  247. ON T1.scid = T2.school_class_id AND T1.en = T2.exam_number
  248. ";
  249. await _rep.SqlNonQueryAsync(insertSql);
  250. selects.Clear();
  251. }
  252. }
  253. #endregion
  254. }
  255. catch (Exception ex)
  256. {
  257. throw new Exception(ex.Message);
  258. }
  259. finally
  260. {
  261. File.Delete(filePath);
  262. }
  263. }
  264. /// <summary>
  265. /// 批量导入学生总成绩
  266. /// </summary>
  267. /// <param name="filePath"></param>
  268. /// <param name="examPlanId"></param>
  269. /// <returns></returns>
  270. public async Task UploadImportStudentTotalScore(string filePath, int examPlanId)
  271. {
  272. /* ----------------------------------------------------------
  273. * 处理步骤:
  274. * 1.验证表头
  275. * 2.读取数据
  276. * 3.处理班级
  277. * 4.批量插入
  278. ---------------------------------------------------------- */
  279. try
  280. {
  281. #region 1.验证表结构
  282. using FileStream fs = new(filePath, FileMode.Open, FileAccess.Read);
  283. IWorkbook workbook = ExcelUtil.GetWorkbook(filePath, fs);
  284. var sheet = workbook.GetSheetAt(0);
  285. var rows = sheet.GetRowEnumerator();
  286. // 少于2行验证
  287. if (sheet.LastRowNum < 2)
  288. {
  289. //"第一行应为填写说明,第二行应为标题行,请勿修改模板结构。"
  290. return;
  291. }
  292. // 读取表头
  293. rows.MoveNext();
  294. IRow headerRow = (IRow)rows.Current;
  295. int index = 0;
  296. int SAMPLE_TYPE_INDEX = index++;
  297. int SCHOOL_ID_INDEX = index++;
  298. int SCHOOL_NAME_INDEX = index++;
  299. int SCHOOL_BID_INDEX = index++;
  300. int SCHOOL_BNAME_INDEX = index++;
  301. int NAME_INDEX = index++;
  302. int CERT_TYPE_INDEX = index++;
  303. int ID_NUM_INDEX = index++;
  304. int EXAM_NUMBER_INDEX = index++;
  305. int COURSE_COMB_INDEX = index++;
  306. int GRADE_INDEX = index++;
  307. int CLASS_INDEX = index++;
  308. int SP_INDEX = index++;
  309. int ABSENT_REPLACE_INDEX = index++;
  310. int COURSE_START_INDEX = index;
  311. Dictionary<int, string> headers = new()
  312. {
  313. { SAMPLE_TYPE_INDEX, "抽样类型" },
  314. { SCHOOL_ID_INDEX, "学校ID" },
  315. { SCHOOL_NAME_INDEX, "学校" },
  316. { SCHOOL_BID_INDEX, "校区ID" },
  317. { SCHOOL_BNAME_INDEX, "校区" },
  318. { NAME_INDEX, "姓名" },
  319. { CERT_TYPE_INDEX, "证件类型" },
  320. { ID_NUM_INDEX, "证件号码" },
  321. { EXAM_NUMBER_INDEX, "考号" },
  322. { COURSE_COMB_INDEX, "选科组合" },
  323. { GRADE_INDEX, "年级" },
  324. { CLASS_INDEX, "班级" },
  325. { SP_INDEX, "特殊学生" },
  326. { ABSENT_REPLACE_INDEX, "缺测替补" },
  327. };
  328. List<string> headerErrors = [];
  329. for (int i = 0; i < COURSE_START_INDEX; i++)
  330. {
  331. if (headerRow.GetCell(i)?.ToString() != headers[i])
  332. {
  333. char letter = (char)('A' + i);
  334. headerErrors.Add(letter.ToString());
  335. }
  336. }
  337. if (headerErrors.Count != 0)
  338. {
  339. string columnErrors = string.Join("、", headerErrors);
  340. //result.ErrorMessage.Add($"第1行标题行{columnErrors}列名错误。从A列开始依次应为抽样类型、学校ID、学校、姓名、证件类型、证件号码、考号、年级、班级。");
  341. //return result;
  342. return;
  343. }
  344. //result.StructureCorrect = true;
  345. #endregion
  346. #region 2.读取数据
  347. var examPlan = await _rep.Change<ExamPlan>().DetachedEntities.FirstOrDefaultAsync(t => t.Id == examPlanId) ?? throw Oops.Oh(ErrorCode.E2001, "监测计划");
  348. var examGradeDict = (await _examGradeService.GetListByExamPlanId(examPlanId)).ToDictionary(t => t.Grade.GradeNumber);
  349. var courseDict = (await _rep.Change<Course>().DetachedEntities.ToListAsync()).ToDictionary(t => t.Name);
  350. var examCourses = await _rep.Change<ExamCourse>().DetachedEntities.Where(t => t.ExamPlanId == examPlanId).Select(t => new { t.GradeId, t.CourseId }).ToListAsync();
  351. // 获取需要导入的科目列表
  352. Dictionary<int, Course> courses = [];
  353. for (int gi = COURSE_START_INDEX; gi < headerRow.LastCellNum; gi++)
  354. {
  355. var cn = headerRow.GetCell(gi).ToString()?.ClearWhitespace() ?? "";
  356. if (string.IsNullOrEmpty(cn))
  357. {
  358. continue;
  359. }
  360. courses.Add(gi, courseDict[cn]);
  361. }
  362. int rn = 1;
  363. List<ExamScoreImportDto> sourceItems = [];
  364. while (rows.MoveNext())
  365. {
  366. rn++;
  367. IRow row = (IRow)rows.Current;
  368. string rv = row.GetCell(0)?.ToString().Trim() ?? "";
  369. if (rv == "")
  370. {
  371. break;
  372. }
  373. var sampleType = (ExamSampleType)row.GetCell(SAMPLE_TYPE_INDEX).NumericCellValue;
  374. var sysOrgId = (short)row.GetCell(SCHOOL_ID_INDEX).NumericCellValue;
  375. var gradeNumber = (short)row.GetCell(GRADE_INDEX).NumericCellValue;
  376. var classNumber = (short)row.GetCell(CLASS_INDEX).NumericCellValue;
  377. var spStr = row.GetCell(SP_INDEX)?.ToString()?.Trim() ?? "";
  378. var isSp = spStr.Contains("特殊");
  379. var absentStr = row.GetCell(ABSENT_REPLACE_INDEX)?.ToString()?.Trim() ?? "";
  380. string remark = isSp ? spStr : "";
  381. if (absentStr.Length > 0)
  382. {
  383. if (remark.Length == 0)
  384. {
  385. remark = absentStr;
  386. }
  387. else
  388. {
  389. remark = $"{remark},{absentStr}";
  390. }
  391. }
  392. for (int gi = COURSE_START_INDEX; gi < headerRow.LastCellNum; gi++)
  393. {
  394. var course = courses[gi];
  395. var eg = examGradeDict[gradeNumber];
  396. // 跳过未监测科目
  397. if (!examCourses.Any(t => t.GradeId == eg.GradeId && t.CourseId == course.Id))
  398. {
  399. continue;
  400. }
  401. var sbid = row.GetCell(SCHOOL_BID_INDEX)?.NumericCellValue;
  402. var ccid = row.GetCell(COURSE_COMB_INDEX)?.NumericCellValue;
  403. ExamScoreImportDto item = new()
  404. {
  405. RowNumber = rn,
  406. SampleType = sampleType,
  407. SysOrgId = sysOrgId,
  408. SysOrgBranchId = sbid > 0 ? (short)sbid : null,
  409. StudentName = (row.GetCell(NAME_INDEX)?.ToString() ?? "").ClearWhitespace(),
  410. CertificateType = (CertificateType)row.GetCell(CERT_TYPE_INDEX).NumericCellValue,
  411. IdNumber = (row.GetCell(ID_NUM_INDEX)?.ToString() ?? "").ClearWhitespace(),
  412. ExamNumber = (row.GetCell(EXAM_NUMBER_INDEX)?.ToString() ?? "").ClearWhitespace(),
  413. GradeNumber = gradeNumber,
  414. ClassNumber = classNumber,
  415. CourseId = course.Id,
  416. NceeCourseCombId = ccid > 0 ? (short)ccid : null,
  417. Remark = remark,
  418. };
  419. string scoreStr = row.GetCell(gi)?.ToString() ?? "";
  420. if (decimal.TryParse(scoreStr, out decimal score))
  421. {
  422. item.Score = score;
  423. }
  424. else if (scoreStr.Contains('特') || scoreStr.Contains('缺'))
  425. {
  426. item.IsExcluded = true;
  427. item.IsAbsent = scoreStr.Contains('缺');
  428. }
  429. if (isSp)
  430. {
  431. item.IsSpecial = true;
  432. item.IsExcluded = true;
  433. }
  434. if (item.IdNumber.Length == 18)
  435. {
  436. if (int.TryParse(item.IdNumber.AsSpan(16, 1), out int gn))
  437. {
  438. item.Gender = (short)(gn % 2 == 0 ? 2 : 1);
  439. }
  440. }
  441. item.ExamGradeId = eg.Id;
  442. item.GradeId = eg.GradeId;
  443. item.GradeBeginYear = eg.GradeBeginYear;
  444. item.GradeEndYear = eg.GradeEndYear;
  445. item.SchoolClassId = _schoolClassService.GetSchoolClassId(examPlan.EducationStage, item.SysOrgId, eg.GradeBeginYear, item.ClassNumber);
  446. sourceItems.Add(item);
  447. }
  448. }
  449. workbook.Close();
  450. fs.Close();
  451. #endregion
  452. #region 3.处理班级
  453. var scs = sourceItems.GroupBy(t => t.SchoolClassId).Select(t => new BatchInsertSchoolClassInput
  454. {
  455. Id = t.Key,
  456. SysOrgId = t.FirstOrDefault().SysOrgId,
  457. SysOrgBranchId = t.FirstOrDefault().SysOrgBranchId,
  458. EducationStage = examPlan.EducationStage,
  459. EducationYears = examPlan.EducationYears,
  460. ClassNumber = t.FirstOrDefault().ClassNumber,
  461. GradeBeginYear = t.FirstOrDefault().GradeBeginYear,
  462. GradeEndYear = t.FirstOrDefault().GradeEndYear,
  463. }).Distinct().ToList();
  464. // 插入没有的班级
  465. await _schoolClassService.BatchInsert(scs);
  466. #endregion
  467. #region 4.批量导入
  468. // 清理成绩
  469. string deleteScoreSql = $"DELETE FROM exam_score WHERE exam_plan_id = {examPlanId};";
  470. //await _rep.SqlNonQueryAsync(deleteScoreSql);
  471. await deleteScoreSql.SetCommandTimeout(60000).SqlNonQueryAsync();
  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 = T1.course_count 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. }