ExamTeacherCourseService.cs 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489
  1. using Furion.DatabaseAccessor.Extensions;
  2. using NPOI.SS.UserModel;
  3. using YBEE.EQM.Core;
  4. namespace YBEE.EQM.Application;
  5. /// <summary>
  6. /// 监测教师任教科目管理服务
  7. /// </summary>
  8. public class ExamTeacherCourseService : IExamTeacherCourseService, ITransient
  9. {
  10. private readonly IRepository<ExamTeacherCourse> _rep;
  11. private readonly ISysDictDataService _sysDictDataService;
  12. private readonly IExamGradeService _examGradeService;
  13. private readonly ISchoolClassService _schoolClassService;
  14. private readonly ICourseService _courseService;
  15. private readonly IExportExcelService _exportExcelService;
  16. public ExamTeacherCourseService(IRepository<ExamTeacherCourse> rep, ISysDictDataService sysDictDataService, IExamGradeService examGradeService, ISchoolClassService schoolClassService, ICourseService courseService, IExportExcelService exportExcelService)
  17. {
  18. _rep = rep;
  19. _sysDictDataService = sysDictDataService;
  20. _examGradeService = examGradeService;
  21. _schoolClassService = schoolClassService;
  22. _courseService = courseService;
  23. _exportExcelService = exportExcelService;
  24. }
  25. #region 批量导入
  26. /// <summary>
  27. /// 上传监测教师批量导入文件
  28. /// </summary>
  29. /// <param name="filePath"></param>
  30. /// <param name="examPlanId"></param>
  31. /// <returns></returns>
  32. public async Task<UploadExamDataOutput<UploadExamTeacherCourseOutput>> Upload(string filePath, int examPlanId)
  33. {
  34. UploadExamDataOutput<UploadExamTeacherCourseOutput> result = new();
  35. try
  36. {
  37. using FileStream fs = new(filePath, FileMode.Open, FileAccess.Read);
  38. IWorkbook workbook = ExcelUtil.GetWorkbook(filePath, fs);
  39. var sheet = workbook.GetSheetAt(0);
  40. var rows = sheet.GetRowEnumerator();
  41. #region 验证表结构
  42. // 少于2行验证
  43. if (sheet.LastRowNum < 2)
  44. {
  45. result.ErrorMessage.Add("第一行应为填写说明,第二行应为标题行,请勿修改模板结构。");
  46. return result;
  47. }
  48. // 跳过第一行
  49. rows.MoveNext();
  50. // 读取表头
  51. rows.MoveNext();
  52. IRow headerRow = (IRow)rows.Current;
  53. int index = 0;
  54. int GRADE_INDEX = index++;
  55. int CLASS_INDEX = index++;
  56. int COURSE_INDEX = index++;
  57. int NAME_INDEX = index++;
  58. int CERT_TYPE_INDEX = index++;
  59. int ID_NUM_INDEX = index++;
  60. int REMARK_INDEX = index;
  61. Dictionary<int, string> headers = new()
  62. {
  63. { GRADE_INDEX, "年级号" },
  64. { CLASS_INDEX, "班级号" },
  65. { COURSE_INDEX, "科目" },
  66. { NAME_INDEX, "教师姓名" },
  67. { CERT_TYPE_INDEX, "证件类型" },
  68. { ID_NUM_INDEX, "证件号码" },
  69. { REMARK_INDEX, "备注" },
  70. };
  71. List<string> headerErrors = new();
  72. for (int i = 0; i <= index; i++)
  73. {
  74. if (headerRow.GetCell(i)?.ToString() != headers[i])
  75. {
  76. char letter = (char)('A' + i);
  77. headerErrors.Add(letter.ToString());
  78. }
  79. }
  80. if (headerErrors.Any())
  81. {
  82. string columnErrors = string.Join("、", headerErrors);
  83. result.ErrorMessage.Add($"第2行标题行{columnErrors}列名错误。从A列开始依次应为年级号、班级号、科目、教师姓名、证件类型、证件号码和备注。");
  84. return result;
  85. }
  86. result.StructureCorrect = true;
  87. #endregion
  88. #region 处理数据
  89. // 监测年级
  90. var examGrades = await _examGradeService.GetListByExamPlanId(examPlanId);
  91. // 获取证件类型
  92. var cts = await _sysDictDataService.GetListByDictTypeId(304);
  93. var certificateTypes = cts.ToDictionary(x => x.Name, y => y.Value);
  94. // 学科
  95. var cs = await _courseService.GetAllLiteList();
  96. var courses = cs.ToDictionary(x => x.Name, y => y.Id);
  97. // 读取数据
  98. List<UploadExamTeacherCourseOutput> data = new();
  99. int rn = 0;
  100. while (rows.MoveNext())
  101. {
  102. IRow row = (IRow)rows.Current;
  103. string rv = row.GetCell(0)?.ToString().Trim() ?? "";
  104. rv += row.GetCell(1)?.ToString().Trim() ?? "";
  105. if (rv == "")
  106. {
  107. break;
  108. }
  109. UploadExamTeacherCourseOutput item = new() { RowNumber = ++rn };
  110. // 年级
  111. if (short.TryParse(row.GetCell(GRADE_INDEX)?.ToString(), out short gradeNumber))
  112. {
  113. var g = examGrades.FirstOrDefault(t => t.Grade.GradeNumber == gradeNumber);
  114. if (g == null)
  115. {
  116. item.ErrorMessage.Add($"{headers[GRADE_INDEX]}与监测年级不符");
  117. }
  118. else
  119. {
  120. item.ExamGradeId = g.Id;
  121. item.GradeId = g.GradeId;
  122. }
  123. }
  124. else
  125. {
  126. item.ErrorMessage.Add(headers[CLASS_INDEX]);
  127. }
  128. // 班级
  129. if (short.TryParse(row.GetCell(CLASS_INDEX)?.ToString(), out short classNumber))
  130. {
  131. item.ClassNumber = classNumber;
  132. if (item.ClassNumber < 1 || item.ClassNumber > 35)
  133. {
  134. item.ErrorMessage.Add($"{headers[CLASS_INDEX]}超限");
  135. }
  136. }
  137. else
  138. {
  139. item.ErrorMessage.Add(headers[CLASS_INDEX]);
  140. }
  141. // 科目
  142. item.CourseName = StringUtil.ClearWhite(row.GetCell(COURSE_INDEX)?.ToString() ?? "");
  143. if (item.CourseName == null)
  144. {
  145. item.ErrorMessage.Add($"{headers[COURSE_INDEX]}未填");
  146. }
  147. else
  148. {
  149. var cn = item.CourseName;
  150. if (cn.Contains("政治") || cn.Contains("道法") || cn.Contains("道德") || cn.Contains("法治"))
  151. {
  152. cn = "政治";
  153. }
  154. if (courses.TryGetValue(cn, out short value))
  155. {
  156. item.CourseId = value;
  157. }
  158. else
  159. {
  160. item.ErrorMessage.Add($"{headers[COURSE_INDEX]}错误");
  161. }
  162. }
  163. // 姓名
  164. item.Name = StringUtil.ClearWhite(row.GetCell(NAME_INDEX)?.ToString() ?? "");
  165. if (item.Name == "" || item.Name.Length > 100)
  166. {
  167. item.ErrorMessage.Add(headers[NAME_INDEX]);
  168. }
  169. if (item.Name.Length > 100) { item.Name = item.Name[..100]; }
  170. // 证件类型
  171. item.CertificateTypeName = StringUtil.ClearWhite(row.GetCell(CERT_TYPE_INDEX)?.ToString() ?? "");
  172. if (!(item.CertificateTypeName != "" && certificateTypes.ContainsKey(item.CertificateTypeName)))
  173. {
  174. item.ErrorMessage.Add(headers[CERT_TYPE_INDEX]);
  175. }
  176. else
  177. {
  178. item.CertificateType = (CertificateType)certificateTypes[item.CertificateTypeName];
  179. }
  180. // 证件号码
  181. item.IdNumber = StringUtil.ClearWhite(row.GetCell(ID_NUM_INDEX)?.ToString() ?? "").ToUpper();
  182. if (item.CertificateType == CertificateType.ID_CARD)
  183. {
  184. var idNumberValidate = CertificateNumberValidator.ValidateIdCard(item.IdNumber);
  185. if (!idNumberValidate.Success)
  186. {
  187. item.ErrorMessage.Add($"{item.CertificateTypeName}{idNumberValidate.ErrorMessage}");
  188. }
  189. }
  190. if (item.IdNumber.Length > 100) { item.IdNumber = item.IdNumber[..100]; }
  191. // 备注
  192. item.Remark = StringUtil.ClearWhite(row.GetCell(REMARK_INDEX)?.ToString() ?? "");
  193. if (item.Remark.Length > 200) { item.Remark = item.Remark[..200]; }
  194. // 行是否验证通过
  195. item.IsSuccess = item.ErrorMessage.Count == 0;
  196. data.Add(item);
  197. result.TotalRowCount++;
  198. if (!item.IsSuccess)
  199. {
  200. result.ErrorRowCount++;
  201. }
  202. }
  203. result.Rows = data;
  204. #endregion
  205. workbook.Close();
  206. fs.Close();
  207. }
  208. catch (Exception ex)
  209. {
  210. throw new Exception(ex.Message);
  211. }
  212. finally
  213. {
  214. File.Delete(filePath);
  215. }
  216. return result;
  217. }
  218. /// <summary>
  219. /// 批量导入监测教师
  220. /// </summary>
  221. /// <param name="input"></param>
  222. /// <returns></returns>
  223. public async Task Import(ImportExamTeacherCourseInput input)
  224. {
  225. var orgId = input.SysOrgId ?? CurrentSysUserInfo.SysOrgId;
  226. // 删除同一监测计划中监测机构内所有数据
  227. await _rep.Where(t => t.ExamPlanId == input.ExamPlanId && t.SysOrgId == orgId && t.SysOrgBranchId == input.SysOrgBranchId).ExecuteDeleteAsync();
  228. List<ExamTeacherCourse> items = new();
  229. var gs = input.Items.Select(t => t.ExamGradeId).Distinct().ToList();
  230. var examGrades = await _rep.Change<ExamGrade>().DetachedEntities.Where(t => gs.Contains(t.Id)).Select(t => t.Adapt<ExamGradeOutput>()).ToListAsync();
  231. foreach (var eg in examGrades)
  232. {
  233. var classNumbers = input.Items.Where(t => t.ExamGradeId == eg.Id).Select(t => t.ClassNumber).Distinct().ToList();
  234. var classDict = await _schoolClassService.GetImportSchoolClassList(new()
  235. {
  236. SysOrgId = orgId,
  237. SysOrgBranchId = input.SysOrgBranchId,
  238. ExamGrade = eg,
  239. ClassNumberList = classNumbers,
  240. });
  241. var citems = input.Items.Where(t => t.ExamGradeId == eg.Id).ToList();
  242. foreach (var ni in citems)
  243. {
  244. var item = ni.Adapt<ExamTeacherCourse>();
  245. item.ExamPlanId = input.ExamPlanId;
  246. item.SysOrgId = orgId;
  247. item.SysOrgBranchId = input.SysOrgBranchId;
  248. item.SchoolClassId = classDict[ni.ClassNumber];
  249. item.Name = StringUtil.ClearWhite(item.Name);
  250. item.IdNumber = StringUtil.ClearIdNumber(item.IdNumber);
  251. items.Add(item);
  252. }
  253. }
  254. await _rep.InsertAsync(items);
  255. }
  256. #endregion
  257. #region 批量导出
  258. /// <summary>
  259. /// 导出TQES导入文件格式文件包
  260. /// </summary>
  261. /// <param name="examPlanId"></param>
  262. /// <returns></returns>
  263. /// <exception cref="Exception"></exception>
  264. public async Task<(string, byte[])> ExportTqesFile(int examPlanId)
  265. {
  266. var examPlan = await _rep.Change<ExamPlan>().DetachedEntities.ProjectToType<ExamPlanOutput>().FirstOrDefaultAsync(t => t.Id == examPlanId) ?? throw Oops.Oh(ErrorCode.E2001);
  267. // 临时存放目录
  268. string fileRoot = Path.Combine(FileUtil.GetTempFileRoot(), $"{Guid.NewGuid()}");
  269. Directory.CreateDirectory(fileRoot);
  270. string filePath = Path.Combine(fileRoot, $"{examPlan.Name}-教师任教科目");
  271. Directory.CreateDirectory(filePath);
  272. try
  273. {
  274. // 定义EXCEL列
  275. List<ExportExcelColDto<ExamTeacherCourseOutput>> noCols = new()
  276. {
  277. new() { Name = "年级号", Width = 8, GetCellValue = (r) => r.ExamGrade.Grade.GradeNumber2 },
  278. new() { Name = "班级号", Width = 8, GetCellValue = (r) => r.ClassNumber },
  279. new() { Name = "科目", Width = 8, GetCellValue = (r) => r.Course.Name },
  280. new() { Name = "身份证号码", Width = 20, GetCellValue = (r) => r.IdNumber },
  281. new() { Name = "姓名", Width = 20, GetCellValue = (r) => r.Name },
  282. };
  283. var items = await _rep.DetachedEntities.Where(t => t.ExamPlanId == examPlanId).ProjectToType<ExamTeacherCourseOutput>().ToListAsync();
  284. var orgs = items.Select(t => new { t.SysOrg.Id, t.SysOrg.Name, t.SysOrg.TqesCode }).Distinct().ToList();
  285. foreach (var org in orgs)
  286. {
  287. var orgData = items.Where(t => t.SysOrgId == org.Id).ToList();
  288. var bs = _exportExcelService.ExportExcel(new ExportExcelDto<ExamTeacherCourseOutput>()
  289. {
  290. IsXlsx = false,
  291. Title = null,
  292. Columns = noCols,
  293. Items = orgData,
  294. IncludeExportTime = false,
  295. });
  296. await File.WriteAllBytesAsync(Path.Combine(filePath, $"T-{org.TqesCode}-{org.Name}-教师任教科目.xls"), bs);
  297. }
  298. string outFileName = $"{examPlan.Name}-教师任教科目.zip";
  299. string outFilePath = Path.Combine(fileRoot, outFileName);
  300. ICSharpCode.SharpZipLib.Zip.FastZip zip = new();
  301. zip.CreateZip(outFilePath, filePath, true, string.Empty);
  302. var retBytes = await File.ReadAllBytesAsync(outFilePath);
  303. return (outFileName, retBytes);
  304. }
  305. catch (Exception ex)
  306. {
  307. throw new Exception("导出错误", ex);
  308. }
  309. finally
  310. {
  311. Directory.Delete(fileRoot, true);
  312. }
  313. }
  314. #endregion
  315. #region 创建编辑
  316. /// <summary>
  317. /// 添加监测教师
  318. /// </summary>
  319. /// <param name="input"></param>
  320. /// <returns></returns>
  321. public async Task Add(AddExamTeacherCourseInput input)
  322. {
  323. var orgId = input.SysOrgId ?? CurrentSysUserInfo.SysOrgId;
  324. // 检测同年级同班同科目是否已经有教师
  325. var sameItems = await _rep.DetachedEntities
  326. .Where(t => t.ExamPlanId == input.ExamPlanId && t.SysOrgId == orgId && t.GradeId == input.GradeId && t.ClassNumber == input.ClassNumber && t.CourseId == input.CourseId)
  327. .ProjectToType<ExamTeacherCourseOutput>().ToListAsync();
  328. if (sameItems.Any())
  329. {
  330. throw Oops.Oh(ErrorCode.E2003);
  331. }
  332. var examGrade = await _examGradeService.GetById(input.ExamGradeId);
  333. var schoolClass = await _schoolClassService.GetSchoolClass(orgId, input.SysOrgBranchId, examGrade, input.ClassNumber);
  334. var item = input.Adapt<ExamTeacherCourse>();
  335. item.SysOrgId = orgId;
  336. item.SchoolClassId = schoolClass.Id;
  337. item.Name = StringUtil.ClearWhite(item.Name);
  338. item.IdNumber = StringUtil.ClearIdNumber(item.IdNumber);
  339. await item.InsertAsync();
  340. }
  341. /// <summary>
  342. /// 更新监测教师
  343. /// </summary>
  344. /// <param name="input"></param>
  345. /// <returns></returns>
  346. public async Task Update(UpdateExamTeacherCourseInput input)
  347. {
  348. var orgId = input.SysOrgId ?? CurrentSysUserInfo.SysOrgId;
  349. var oitem = await _rep.DetachedEntities.FirstOrDefaultAsync(t => t.Id == input.Id) ?? throw Oops.Oh(ErrorCode.E2001);
  350. // 检测同年级同班同科目是否已经有教师
  351. var sameItems = await _rep.DetachedEntities
  352. .Where(t => t.Id != input.Id && t.ExamPlanId == input.ExamPlanId && t.SysOrgId == orgId && t.GradeId == input.GradeId && t.ClassNumber == input.ClassNumber && t.CourseId == input.CourseId)
  353. .ProjectToType<ExamTeacherCourseOutput>().ToListAsync();
  354. if (sameItems.Any())
  355. {
  356. throw Oops.Oh(ErrorCode.E2003);
  357. }
  358. var examGrade = await _examGradeService.GetById(oitem.ExamGradeId);
  359. var schoolClass = await _schoolClassService.GetSchoolClass(oitem.SysOrgId, input.SysOrgBranchId, examGrade, input.ClassNumber);
  360. var item = input.Adapt<ExamTeacherCourse>();
  361. item.SchoolClassId = schoolClass.Id;
  362. item.Name = StringUtil.ClearWhite(item.Name);
  363. item.IdNumber = StringUtil.ClearIdNumber(item.IdNumber);
  364. await item.UpdateIncludeNowAsync(new[] {
  365. nameof(item.Name),
  366. nameof(item.CertificateType),
  367. nameof(item.IdNumber),
  368. nameof(item.ExamGradeId),
  369. nameof(item.GradeId),
  370. nameof(item.SchoolClassId),
  371. nameof(item.SysOrgBranchId),
  372. nameof(item.ClassNumber),
  373. nameof(item.CourseId),
  374. nameof(item.Remark),
  375. });
  376. var items = await _rep.Where(t => t.Id != input.Id && t.ExamPlanId == input.ExamPlanId && t.SysOrgId == orgId && t.Name == oitem.Name && t.CertificateType == input.CertificateType && t.IdNumber == input.IdNumber).ToListAsync();
  377. if (items.Any())
  378. {
  379. foreach (var uitem in items)
  380. {
  381. uitem.Name = input.Name;
  382. uitem.CertificateType = input.CertificateType;
  383. uitem.IdNumber = input.IdNumber;
  384. await uitem.UpdateIncludeNowAsync(new[] { nameof(uitem.Name), nameof(uitem.CertificateType), nameof(uitem.IdNumber) });
  385. }
  386. }
  387. }
  388. /// <summary>
  389. /// 删除监测教师
  390. /// </summary>
  391. /// <param name="input"></param>
  392. /// <returns></returns>
  393. public async Task Del(BaseId input)
  394. {
  395. var item = await _rep.FirstOrDefaultAsync(t => t.Id == input.Id) ?? throw Oops.Oh(ErrorCode.E2001);
  396. await item.DeleteAsync();
  397. }
  398. /// <summary>
  399. /// 清空监测教师
  400. /// </summary>
  401. /// <param name="input"></param>
  402. /// <returns></returns>
  403. public async Task Clear(ClearExamTeacherCourseInput input)
  404. {
  405. var orgId = CurrentSysUserInfo.SysOrgId;
  406. await _rep.Where(t => t.ExamPlanId == input.ExamPlanId && t.SysOrgId == orgId).ExecuteDeleteAsync();
  407. }
  408. #endregion
  409. #region 查询统计
  410. /// <summary>
  411. /// 分页查询监测教师列表
  412. /// </summary>
  413. /// <param name="input"></param>
  414. /// <returns></returns>
  415. public async Task<PageResult<ExamTeacherCourseOutput>> QueryPageList(ExamTeacherCoursePageInput input)
  416. {
  417. var query = GetQueryBase(input);
  418. query = query.Where(t => t.ExamPlanId == input.ExamPlanId && t.SysOrgId == CurrentSysUserInfo.SysOrgId);
  419. var ret = await query.OrderBy(t => t.Id).ProjectToType<ExamTeacherCourseOutput>().ToADPagedListAsync(input.PageIndex, input.PageSize);
  420. return ret;
  421. }
  422. #endregion
  423. #region 私有方法
  424. /// <summary>
  425. /// 构建查询
  426. /// </summary>
  427. /// <param name="input"></param>
  428. /// <returns></returns>
  429. private IQueryable<ExamTeacherCourse> GetQueryBase(ExamTeacherCoursePageInput input)
  430. {
  431. var name = !string.IsNullOrEmpty(input.Name?.Trim());
  432. var idNumber = !string.IsNullOrEmpty(input.IdNumber?.Trim());
  433. var query = _rep.DetachedEntities.Where((name, u => EF.Functions.Like(u.Name, $"%{input.Name.Trim()}%")))
  434. .Where((idNumber, u => EF.Functions.Like(u.IdNumber, $"%{input.IdNumber.Trim()}%")))
  435. .Where(input.CertificateType.HasValue, t => t.CertificateType == input.CertificateType)
  436. .Where(input.ClassNumber.HasValue, t => t.ClassNumber == input.ClassNumber)
  437. .Where(input.SysOrgBranchId.HasValue, t => t.SysOrgBranchId == input.SysOrgBranchId)
  438. .Where(input.GradeId.HasValue, t => t.GradeId == input.GradeId)
  439. .Where(input.CourseId.HasValue, t => t.CourseId == input.CourseId);
  440. return query;
  441. }
  442. #endregion
  443. }