ExamTeacherCourseService.cs 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418
  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. public ExamTeacherCourseService(IRepository<ExamTeacherCourse> rep, ISysDictDataService sysDictDataService, IExamGradeService examGradeService, ISchoolClassService schoolClassService, ICourseService courseService)
  16. {
  17. _rep = rep;
  18. _sysDictDataService = sysDictDataService;
  19. _examGradeService = examGradeService;
  20. _schoolClassService = schoolClassService;
  21. _courseService = courseService;
  22. }
  23. #region 批量导入
  24. /// <summary>
  25. /// 上传监测教师批量导入文件
  26. /// </summary>
  27. /// <param name="filePath"></param>
  28. /// <param name="examPlanId"></param>
  29. /// <returns></returns>
  30. public async Task<UploadExamDataOutput<UploadExamTeacherCourseOutput>> Upload(string filePath, int examPlanId)
  31. {
  32. UploadExamDataOutput<UploadExamTeacherCourseOutput> result = new();
  33. try
  34. {
  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. #region 验证表结构
  40. // 少于2行验证
  41. if (sheet.LastRowNum < 2)
  42. {
  43. result.ErrorMessage.Add("第一行应为填写说明,第二行应为标题行,请勿修改模板结构。");
  44. return result;
  45. }
  46. // 跳过第一行
  47. rows.MoveNext();
  48. // 读取表头
  49. rows.MoveNext();
  50. IRow headerRow = (IRow)rows.Current;
  51. int index = 0;
  52. int GRADE_INDEX = index++;
  53. int CLASS_INDEX = index++;
  54. int COURSE_INDEX = index++;
  55. int NAME_INDEX = index++;
  56. int CERT_TYPE_INDEX = index++;
  57. int ID_NUM_INDEX = index++;
  58. int REMARK_INDEX = index;
  59. Dictionary<int, string> headers = new()
  60. {
  61. { GRADE_INDEX, "年级号" },
  62. { CLASS_INDEX, "班级号" },
  63. { COURSE_INDEX, "科目" },
  64. { NAME_INDEX, "教师姓名" },
  65. { CERT_TYPE_INDEX, "证件类型" },
  66. { ID_NUM_INDEX, "证件号码" },
  67. { REMARK_INDEX, "备注" },
  68. };
  69. List<string> headerErrors = new();
  70. for (int i = 0; i <= index; i++)
  71. {
  72. if (headerRow.GetCell(i)?.ToString() != headers[i])
  73. {
  74. char letter = (char)('A' + i);
  75. headerErrors.Add(letter.ToString());
  76. }
  77. }
  78. if (headerErrors.Any())
  79. {
  80. string columnErrors = string.Join("、", headerErrors);
  81. result.ErrorMessage.Add($"第2行标题行{columnErrors}列名错误。从A列开始依次应为年级号、班级号、科目、教师姓名、证件类型、证件号码和备注。");
  82. return result;
  83. }
  84. result.StructureCorrect = true;
  85. #endregion
  86. #region 处理数据
  87. // 监测年级
  88. var examGrades = await _examGradeService.GetListByExamPlanId(examPlanId);
  89. // 获取证件类型
  90. var cts = await _sysDictDataService.GetListByDictTypeId(304);
  91. var certificateTypes = cts.ToDictionary(x => x.Name, y => y.Value);
  92. // 学科
  93. var cs = await _courseService.GetAllLiteList();
  94. var courses = cs.ToDictionary(x => x.Name, y => y.Id);
  95. // 读取数据
  96. List<UploadExamTeacherCourseOutput> data = new();
  97. int rn = 0;
  98. while (rows.MoveNext())
  99. {
  100. IRow row = (IRow)rows.Current;
  101. string rv = row.GetCell(0)?.ToString().Trim() ?? "";
  102. rv += row.GetCell(1)?.ToString().Trim() ?? "";
  103. if (rv == "")
  104. {
  105. break;
  106. }
  107. UploadExamTeacherCourseOutput item = new() { RowNumber = ++rn };
  108. // 年级
  109. if (short.TryParse(row.GetCell(GRADE_INDEX)?.ToString(), out short gradeNumber))
  110. {
  111. var g = examGrades.FirstOrDefault(t => t.Grade.GradeNumber == gradeNumber);
  112. if (g == null)
  113. {
  114. item.ErrorMessage.Add($"{headers[GRADE_INDEX]}与监测年级不符");
  115. }
  116. else
  117. {
  118. item.ExamGradeId = g.Id;
  119. item.GradeId = g.GradeId;
  120. }
  121. }
  122. else
  123. {
  124. item.ErrorMessage.Add(headers[CLASS_INDEX]);
  125. }
  126. // 班级
  127. if (short.TryParse(row.GetCell(CLASS_INDEX)?.ToString(), out short classNumber))
  128. {
  129. item.ClassNumber = classNumber;
  130. if (item.ClassNumber < 1 || item.ClassNumber > 35)
  131. {
  132. item.ErrorMessage.Add($"{headers[CLASS_INDEX]}超限");
  133. }
  134. }
  135. else
  136. {
  137. item.ErrorMessage.Add(headers[CLASS_INDEX]);
  138. }
  139. // 科目
  140. item.CourseName = StringUtil.ClearWhite(row.GetCell(COURSE_INDEX)?.ToString() ?? "");
  141. if (item.CourseName == null)
  142. {
  143. item.ErrorMessage.Add($"{headers[COURSE_INDEX]}未填");
  144. }
  145. else
  146. {
  147. var cn = item.CourseName;
  148. if (cn.Contains("政治") || cn.Contains("道法") || cn.Contains("道德") || cn.Contains("法治"))
  149. {
  150. cn = "政治";
  151. }
  152. if (courses.TryGetValue(cn, out short value))
  153. {
  154. item.CourseId = value;
  155. }
  156. else
  157. {
  158. item.ErrorMessage.Add($"{headers[COURSE_INDEX]}错误");
  159. }
  160. }
  161. // 姓名
  162. item.Name = StringUtil.ClearWhite(row.GetCell(NAME_INDEX)?.ToString() ?? "");
  163. if (item.Name == "" || item.Name.Length > 100)
  164. {
  165. item.ErrorMessage.Add(headers[NAME_INDEX]);
  166. }
  167. if (item.Name.Length > 100) { item.Name = item.Name[..100]; }
  168. // 证件类型
  169. item.CertificateTypeName = StringUtil.ClearWhite(row.GetCell(CERT_TYPE_INDEX)?.ToString() ?? "");
  170. if (!(item.CertificateTypeName != "" && certificateTypes.ContainsKey(item.CertificateTypeName)))
  171. {
  172. item.ErrorMessage.Add(headers[CERT_TYPE_INDEX]);
  173. }
  174. else
  175. {
  176. item.CertificateType = (CertificateType)certificateTypes[item.CertificateTypeName];
  177. }
  178. // 证件号码
  179. item.IdNumber = StringUtil.ClearWhite(row.GetCell(ID_NUM_INDEX)?.ToString() ?? "").ToUpper();
  180. if (item.CertificateType == CertificateType.ID_CARD)
  181. {
  182. var idNumberValidate = CertificateNumberValidator.ValidateIdCard(item.IdNumber);
  183. if (!idNumberValidate.Success)
  184. {
  185. item.ErrorMessage.Add($"{item.CertificateTypeName}{idNumberValidate.ErrorMessage}");
  186. }
  187. }
  188. if (item.IdNumber.Length > 100) { item.IdNumber = item.IdNumber[..100]; }
  189. // 备注
  190. item.Remark = StringUtil.ClearWhite(row.GetCell(REMARK_INDEX)?.ToString() ?? "");
  191. if (item.Remark.Length > 200) { item.Remark = item.Remark[..200]; }
  192. // 行是否验证通过
  193. item.IsSuccess = item.ErrorMessage.Count == 0;
  194. data.Add(item);
  195. result.TotalRowCount++;
  196. if (!item.IsSuccess)
  197. {
  198. result.ErrorRowCount++;
  199. }
  200. }
  201. result.Rows = data;
  202. #endregion
  203. workbook.Close();
  204. fs.Close();
  205. }
  206. catch (Exception ex)
  207. {
  208. throw new Exception(ex.Message);
  209. }
  210. finally
  211. {
  212. File.Delete(filePath);
  213. }
  214. return result;
  215. }
  216. /// <summary>
  217. /// 批量导入监测教师
  218. /// </summary>
  219. /// <param name="input"></param>
  220. /// <returns></returns>
  221. public async Task Import(ImportExamTeacherCourseInput input)
  222. {
  223. var orgId = input.SysOrgId ?? CurrentSysUserInfo.SysOrgId;
  224. // 删除同一监测计划中监测机构内所有数据
  225. await _rep.Where(t => t.ExamPlanId == input.ExamPlanId && t.SysOrgId == orgId && t.SysOrgBranchId == input.SysOrgBranchId).ExecuteDeleteAsync();
  226. List<ExamTeacherCourse> items = new();
  227. var gs = input.Items.Select(t => t.ExamGradeId).Distinct().ToList();
  228. var examGrades = await _rep.Change<ExamGrade>().DetachedEntities.Where(t => gs.Contains(t.Id)).Select(t => t.Adapt<ExamGradeOutput>()).ToListAsync();
  229. foreach (var eg in examGrades)
  230. {
  231. var classNumbers = input.Items.Where(t => t.ExamGradeId == eg.Id).Select(t => t.ClassNumber).Distinct().ToList();
  232. var classDict = await _schoolClassService.GetImportSchoolClassList(new()
  233. {
  234. SysOrgId = orgId,
  235. SysOrgBranchId = input.SysOrgBranchId,
  236. ExamGrade = eg,
  237. ClassNumberList = classNumbers,
  238. });
  239. var citems = input.Items.Where(t => t.ExamGradeId == eg.Id).ToList();
  240. foreach (var ni in citems)
  241. {
  242. var item = ni.Adapt<ExamTeacherCourse>();
  243. item.ExamPlanId = input.ExamPlanId;
  244. item.SysOrgId = orgId;
  245. item.SysOrgBranchId = input.SysOrgBranchId;
  246. item.SchoolClassId = classDict[ni.ClassNumber];
  247. items.Add(item);
  248. }
  249. }
  250. await _rep.InsertAsync(items);
  251. }
  252. #endregion
  253. #region 创建编辑
  254. /// <summary>
  255. /// 添加监测教师
  256. /// </summary>
  257. /// <param name="input"></param>
  258. /// <returns></returns>
  259. public async Task Add(AddExamTeacherCourseInput input)
  260. {
  261. var orgId = input.SysOrgId ?? CurrentSysUserInfo.SysOrgId;
  262. // 检测同年级同班同科目是否已经有教师
  263. var sameItems = await _rep.DetachedEntities
  264. .Where(t => t.ExamPlanId == input.ExamPlanId && t.SysOrgId == orgId && t.GradeId == input.GradeId && t.ClassNumber == input.ClassNumber && t.CourseId == input.CourseId)
  265. .ProjectToType<ExamTeacherCourseOutput>().ToListAsync();
  266. if (sameItems.Any())
  267. {
  268. throw Oops.Oh(ErrorCode.E2003);
  269. }
  270. var examGrade = await _examGradeService.GetById(input.ExamGradeId);
  271. var schoolClass = await _schoolClassService.GetSchoolClass(orgId, input.SysOrgBranchId, examGrade, input.ClassNumber);
  272. var item = input.Adapt<ExamTeacherCourse>();
  273. item.SysOrgId = orgId;
  274. item.SchoolClassId = schoolClass.Id;
  275. await item.InsertAsync();
  276. }
  277. /// <summary>
  278. /// 更新监测教师
  279. /// </summary>
  280. /// <param name="input"></param>
  281. /// <returns></returns>
  282. public async Task Update(UpdateExamTeacherCourseInput input)
  283. {
  284. var orgId = input.SysOrgId ?? CurrentSysUserInfo.SysOrgId;
  285. var oitem = await _rep.DetachedEntities.FirstOrDefaultAsync(t => t.Id == input.Id) ?? throw Oops.Oh(ErrorCode.E2001);
  286. // 检测同年级同班同科目是否已经有教师
  287. var sameItems = await _rep.DetachedEntities
  288. .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)
  289. .ProjectToType<ExamTeacherCourseOutput>().ToListAsync();
  290. if (sameItems.Any())
  291. {
  292. throw Oops.Oh(ErrorCode.E2003);
  293. }
  294. var examGrade = await _examGradeService.GetById(oitem.ExamGradeId);
  295. var schoolClass = await _schoolClassService.GetSchoolClass(oitem.SysOrgId, input.SysOrgBranchId, examGrade, input.ClassNumber);
  296. var item = input.Adapt<ExamTeacherCourse>();
  297. item.SchoolClassId = schoolClass.Id;
  298. await item.UpdateIncludeNowAsync(new[] {
  299. nameof(item.Name),
  300. nameof(item.CertificateType),
  301. nameof(item.IdNumber),
  302. nameof(item.ExamGradeId),
  303. nameof(item.GradeId),
  304. nameof(item.SchoolClassId),
  305. nameof(item.SysOrgBranchId),
  306. nameof(item.ClassNumber),
  307. nameof(item.CourseId),
  308. nameof(item.Remark),
  309. });
  310. 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();
  311. if (items.Any())
  312. {
  313. foreach (var uitem in items)
  314. {
  315. uitem.Name = input.Name;
  316. uitem.CertificateType = input.CertificateType;
  317. uitem.IdNumber = input.IdNumber;
  318. await uitem.UpdateIncludeNowAsync(new[] { nameof(uitem.Name), nameof(uitem.CertificateType), nameof(uitem.IdNumber) });
  319. }
  320. }
  321. }
  322. /// <summary>
  323. /// 删除监测教师
  324. /// </summary>
  325. /// <param name="input"></param>
  326. /// <returns></returns>
  327. public async Task Del(BaseId input)
  328. {
  329. var item = await _rep.FirstOrDefaultAsync(t => t.Id == input.Id) ?? throw Oops.Oh(ErrorCode.E2001);
  330. await item.DeleteAsync();
  331. }
  332. /// <summary>
  333. /// 清空监测教师
  334. /// </summary>
  335. /// <param name="input"></param>
  336. /// <returns></returns>
  337. public async Task Clear(ClearExamTeacherCourseInput input)
  338. {
  339. var orgId = CurrentSysUserInfo.SysOrgId;
  340. await _rep.Where(t => t.ExamPlanId == input.ExamPlanId && t.SysOrgId == orgId).ExecuteDeleteAsync();
  341. }
  342. #endregion
  343. #region 查询统计
  344. /// <summary>
  345. /// 分页查询监测教师列表
  346. /// </summary>
  347. /// <param name="input"></param>
  348. /// <returns></returns>
  349. public async Task<PageResult<ExamTeacherCourseOutput>> QueryPageList(ExamTeacherCoursePageInput input)
  350. {
  351. var query = GetQueryBase(input);
  352. query = query.Where(t => t.ExamPlanId == input.ExamPlanId && t.SysOrgId == CurrentSysUserInfo.SysOrgId);
  353. var ret = await query.OrderBy(t => t.Id).ProjectToType<ExamTeacherCourseOutput>().ToADPagedListAsync(input.PageIndex, input.PageSize);
  354. return ret;
  355. }
  356. #endregion
  357. #region 私有方法
  358. /// <summary>
  359. /// 构建查询
  360. /// </summary>
  361. /// <param name="input"></param>
  362. /// <returns></returns>
  363. private IQueryable<ExamTeacherCourse> GetQueryBase(ExamTeacherCoursePageInput input)
  364. {
  365. var name = !string.IsNullOrEmpty(input.Name?.Trim());
  366. var idNumber = !string.IsNullOrEmpty(input.IdNumber?.Trim());
  367. var query = _rep.DetachedEntities.Where((name, u => EF.Functions.Like(u.Name, $"%{input.Name.Trim()}%")))
  368. .Where((idNumber, u => EF.Functions.Like(u.IdNumber, $"%{input.IdNumber.Trim()}%")))
  369. .Where(input.CertificateType.HasValue, t => t.CertificateType == input.CertificateType)
  370. .Where(input.ClassNumber.HasValue, t => t.ClassNumber == input.ClassNumber)
  371. .Where(input.SysOrgBranchId.HasValue, t => t.SysOrgBranchId == input.SysOrgBranchId)
  372. .Where(input.GradeId.HasValue, t => t.GradeId == input.GradeId)
  373. .Where(input.CourseId.HasValue, t => t.CourseId == input.CourseId);
  374. return query;
  375. }
  376. #endregion
  377. }