ExamSpecialStudentService.cs 35 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800
  1. using Furion.DatabaseAccessor.Extensions;
  2. using Furion.JsonSerialization;
  3. using NPOI.OpenXmlFormats.Wordprocessing;
  4. using NPOI.SS.UserModel;
  5. using NPOI.XWPF.UserModel;
  6. using System.Dynamic;
  7. using YBEE.EQM.Core;
  8. namespace YBEE.EQM.Application;
  9. /// <summary>
  10. /// 监测特殊学生上报管理服务
  11. /// </summary>
  12. public class ExamSpecialStudentService(
  13. IRepository<ExamSpecialStudent> rep,
  14. IExamGradeService examGradeService,
  15. ISysDictDataService sysDictDataService,
  16. ISchoolClassService schoolClassService,
  17. IResourceFileService resourceFileService
  18. ) : IExamSpecialStudentService, ITransient
  19. {
  20. #region 批量导入
  21. /// <summary>
  22. /// 上传监测特殊学生批量导入文件
  23. /// </summary>
  24. /// <param name="filePath"></param>
  25. /// <param name="examPlanId"></param>
  26. /// <returns></returns>
  27. public async Task<UploadExamDataOutput<UploadExamSpecialStudentOutput>> Upload(string filePath, int examPlanId)
  28. {
  29. UploadExamDataOutput<UploadExamSpecialStudentOutput> result = new();
  30. try
  31. {
  32. // 已认证过名单
  33. var spStus = await GetPreIdentifiedList(examPlanId);
  34. using FileStream fs = new(filePath, FileMode.Open, FileAccess.Read);
  35. IWorkbook workbook = ExcelUtil.GetWorkbook(filePath, fs);
  36. var sheet = workbook.GetSheetAt(0);
  37. var rows = sheet.GetRowEnumerator();
  38. #region 验证表结构
  39. // 少于2行验证
  40. if (sheet.LastRowNum < 2)
  41. {
  42. result.ErrorMessage.Add("第一行应为填写说明,第二行应为标题行,请勿修改模板结构。");
  43. return result;
  44. }
  45. // 跳过第一行
  46. rows.MoveNext();
  47. // 读取表头
  48. rows.MoveNext();
  49. IRow headerRow = (IRow)rows.Current;
  50. int index = 0;
  51. int GRADE_INDEX = index++;
  52. int CLASS_INDEX = index++;
  53. int NAME_INDEX = index++;
  54. int CERT_TYPE_INDEX = index++;
  55. int ID_NUM_INDEX = index++;
  56. int GENDER_INDEX = index++;
  57. int REASON_INDEX = index++;
  58. int TEL_INDEX = index++;
  59. int REMARK_INDEX = index;
  60. Dictionary<int, string> headers = new()
  61. {
  62. { GRADE_INDEX, "年级号" },
  63. { CLASS_INDEX, "班级号" },
  64. { NAME_INDEX, "姓名" },
  65. { CERT_TYPE_INDEX, "证件类型" },
  66. { ID_NUM_INDEX, "证件号码" },
  67. { GENDER_INDEX, "性别" },
  68. { REASON_INDEX, "特殊原因" },
  69. { TEL_INDEX, "家长电话" },
  70. { REMARK_INDEX, "备注" },
  71. };
  72. List<string> headerErrors = [];
  73. for (int i = 0; i <= index; i++)
  74. {
  75. if (headerRow.GetCell(i)?.ToString() != headers[i])
  76. {
  77. char letter = (char)('A' + i);
  78. headerErrors.Add(letter.ToString());
  79. }
  80. }
  81. if (headerErrors.Any())
  82. {
  83. string columnErrors = string.Join("、", headerErrors);
  84. result.ErrorMessage.Add($"第2行标题行{columnErrors}列名错误。从A列开始依次应为年级号、班级号、姓名、证件类型、证件号码、特殊原因、家长电话和备注。");
  85. return result;
  86. }
  87. result.StructureCorrect = true;
  88. #endregion
  89. #region 处理数据
  90. // 监测年级
  91. var examGrades = await examGradeService.GetListByExamPlanId(examPlanId);
  92. // 获取证件类型
  93. var cts = await sysDictDataService.GetListByDictTypeId(304);
  94. var certificateTypes = cts.ToDictionary(x => x.Name, y => y.Value);
  95. // 读取数据
  96. List<UploadExamSpecialStudentOutput> data = [];
  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. UploadExamSpecialStudentOutput 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.Name = (row.GetCell(NAME_INDEX)?.ToString() ?? "").ClearWhitespace();
  141. if (item.Name == "" || item.Name.Length > 100)
  142. {
  143. item.ErrorMessage.Add($"{headers[NAME_INDEX]}未填或超出100字");
  144. }
  145. if (item.Name.Length > 100) { item.Name = item.Name[..100]; }
  146. // 证件类型
  147. item.CertificateTypeName = (row.GetCell(CERT_TYPE_INDEX)?.ToString() ?? "").ClearWhitespace();
  148. if (!(item.CertificateTypeName != "" && certificateTypes.ContainsKey(item.CertificateTypeName)))
  149. {
  150. item.ErrorMessage.Add(headers[CERT_TYPE_INDEX]);
  151. }
  152. else
  153. {
  154. item.CertificateType = (CertificateType)certificateTypes[item.CertificateTypeName];
  155. }
  156. // 证件号码
  157. item.IdNumber = (row.GetCell(ID_NUM_INDEX)?.ToString() ?? "").ClearWhitespace().ToUpper();
  158. if (item.CertificateType == CertificateType.ID_CARD)
  159. {
  160. var idNumberValidate = CertificateNumberValidator.ValidateIdCard(item.IdNumber);
  161. if (!idNumberValidate.Success)
  162. {
  163. item.ErrorMessage.Add($"{item.CertificateTypeName}{idNumberValidate.ErrorMessage}");
  164. }
  165. else
  166. {
  167. item.GenderName = idNumberValidate.Gender == Gender.MALE ? "男" : "女";
  168. item.Gender = idNumberValidate.Gender;
  169. }
  170. }
  171. else
  172. {
  173. // 性别
  174. item.GenderName = (row.GetCell(GENDER_INDEX)?.ToString() ?? "").ClearWhitespace();
  175. if (item.CertificateType != CertificateType.ID_CARD)
  176. {
  177. item.Gender = item.GenderName == "男" ? Gender.MALE : item.GenderName == "女" ? Gender.FEMALE : Gender.UNKNOWN;
  178. }
  179. }
  180. if (item.IdNumber.Length > 50) { item.IdNumber = item.IdNumber[..50]; }
  181. if (spStus.Any(t => t.IdNumber == item.IdNumber))
  182. {
  183. item.ErrorMessage.Add("往期已认定不能重复上报");
  184. }
  185. // 特殊原因
  186. item.ApplyReason = (row.GetCell(REASON_INDEX)?.ToString() ?? "").ClearWhitespace();
  187. if (item.ApplyReason == "" || item.ApplyReason.Length > 2000)
  188. {
  189. item.ErrorMessage.Add($"{headers[REASON_INDEX]}未填或超出2000字");
  190. }
  191. if (item.ApplyReason.Length > 2000) { item.ApplyReason = item.ApplyReason[..2000]; }
  192. // 家长电话
  193. item.PatriarchTel = (row.GetCell(TEL_INDEX)?.ToString() ?? "").ClearWhitespace();
  194. if (item.PatriarchTel == "" || item.PatriarchTel.Length > 50)
  195. {
  196. item.ErrorMessage.Add($"{headers[TEL_INDEX]}未填或超出50字");
  197. }
  198. if (item.PatriarchTel.Length > 50) { item.PatriarchTel = item.PatriarchTel[..50]; }
  199. // 备注
  200. item.Remark = (row.GetCell(REMARK_INDEX)?.ToString() ?? "").ClearWhitespace();
  201. if (item.Remark.Length > 200) { item.Remark = item.Remark[..200]; }
  202. // 行是否验证通过
  203. item.IsSuccess = item.ErrorMessage.Count == 0;
  204. data.Add(item);
  205. result.TotalRowCount++;
  206. if (!item.IsSuccess)
  207. {
  208. result.ErrorRowCount++;
  209. }
  210. }
  211. result.Rows = data;
  212. #endregion
  213. workbook.Close();
  214. fs.Close();
  215. }
  216. catch (Exception ex)
  217. {
  218. throw new Exception(ex.Message);
  219. }
  220. finally
  221. {
  222. File.Delete(filePath);
  223. }
  224. return result;
  225. }
  226. /// <summary>
  227. /// 批量导入监测特殊学生
  228. /// </summary>
  229. /// <param name="input"></param>
  230. /// <returns></returns>
  231. public async Task<int> Import(ImportExamSpecialStudentInput input)
  232. {
  233. var orgId = input.SysOrgId ?? CurrentSysUserInfo.SysOrgId;
  234. var existsIdNumberList = await rep.DetachedEntities.Where(t => t.ExamPlanId == input.ExamPlanId && t.SysOrgId == orgId).Select(t => t.IdNumber).Distinct().ToListAsync() ?? [];
  235. //// 删除同一监测计划中监测机构内所有数据
  236. //await _rep.Where(t => t.ExamPlanId == input.ExamPlanId && t.SysOrgId == orgId && t.SysOrgBranchId == input.SysOrgBranchId).ExecuteDeleteAsync();
  237. List<ExamSpecialStudent> items = [];
  238. var spStus = input.Items.Where(t => !existsIdNumberList.Contains(t.IdNumber)).ToList();
  239. var gs = spStus.Select(t => t.ExamGradeId).Distinct().ToList();
  240. var examGrades = await rep.Change<ExamGrade>().DetachedEntities.Where(t => gs.Contains(t.Id)).Select(t => t.Adapt<ExamGradeOutput>()).ToListAsync();
  241. // 往期已认定记录
  242. var preItems = await rep.Change<SpecialStudent>().DetachedEntities.Where(t => t.SysOrgId == orgId).ToListAsync();
  243. var ePreItems = await GetPreIdentifiedList(input.ExamPlanId);
  244. int c = 0;
  245. foreach (var eg in examGrades)
  246. {
  247. var classNumbers = spStus.Where(t => t.ExamGradeId == eg.Id).Select(t => t.ClassNumber).Distinct().ToList();
  248. var classDict = await schoolClassService.GetImportSchoolClassList(new()
  249. {
  250. SysOrgId = orgId,
  251. SysOrgBranchId = input.SysOrgBranchId,
  252. ExamGrade = eg,
  253. ClassNumberList = classNumbers,
  254. });
  255. var citems = spStus.Where(t => t.ExamGradeId == eg.Id).ToList();
  256. foreach (var ni in citems)
  257. {
  258. var item = ni.Adapt<ExamSpecialStudent>();
  259. item.ExamPlanId = input.ExamPlanId;
  260. item.SysOrgId = orgId;
  261. item.SysOrgBranchId = input.SysOrgBranchId;
  262. item.SchoolClassId = classDict[ni.ClassNumber];
  263. item.IsPreIdentified = preItems.Any(t => t.CertificateType == item.CertificateType && t.IdNumber.ToUpper() == item.IdNumber) || ePreItems.Any(t => t.CertificateType == item.CertificateType && t.IdNumber.ToUpper() == item.IdNumber);
  264. if (item.IsPreIdentified)
  265. {
  266. item.Status = AuditStatus.APPROVED;
  267. }
  268. items.Add(item);
  269. c++;
  270. }
  271. }
  272. await rep.InsertAsync(items);
  273. return c;
  274. }
  275. #endregion
  276. #region 创建编辑
  277. /// <summary>
  278. /// 添加监测特殊学生
  279. /// </summary>
  280. /// <param name="input"></param>
  281. /// <returns></returns>
  282. public async Task Add(AddExamSpecialStudentInput input)
  283. {
  284. var item = input.Adapt<ExamSpecialStudent>();
  285. item.SysOrgId = CurrentSysUserInfo.SysOrgId;
  286. item.Name = item.Name?.ClearWhitespace();
  287. item.IdNumber = item.IdNumber?.ClearWhitespace();
  288. var isPreIdentified = await rep.AnyAsync(t => t.ExamPlanId == input.ExamPlanId && t.SysOrgId == item.SysOrgId && t.IsPreIdentified == true && t.IdNumber.ToUpper() == input.IdNumber.ToUpper());
  289. if (isPreIdentified)
  290. {
  291. throw Oops.Oh("往期以认定,不能重复添加");
  292. }
  293. // 检测同一监测计划中同机构内是否有相同证件号码的学生
  294. var sameItems = await rep.DetachedEntities.Where(t => t.ExamPlanId == input.ExamPlanId)
  295. .Where(t => t.SysOrgId == item.SysOrgId)
  296. .Where(t => t.CertificateType == input.CertificateType)
  297. .Where(t => t.IdNumber.ToUpper() == input.IdNumber.ToUpper())
  298. .ProjectToType<ExamSpecialStudentOutput>().ToListAsync();
  299. if (sameItems.Count != 0)
  300. {
  301. throw Oops.Oh(ErrorCode.E2003, string.Join("、", sameItems.Select(t => $"{t.ExamGrade.Grade.Name}{t.ClassNumber}班{t.Name}")), "证件号码");
  302. }
  303. var examGrade = await examGradeService.GetById(input.ExamGradeId);
  304. var schoolClass = await schoolClassService.GetSchoolClass(item.SysOrgId, input.SysOrgBranchId, examGrade, input.ClassNumber);
  305. item.SchoolClassId = schoolClass.Id;
  306. var hstu = await rep.Change<SpecialStudent>().DetachedEntities.AnyAsync(t => t.SysOrgId == item.SysOrgId && t.CertificateType == item.CertificateType && t.IdNumber.ToUpper() == item.IdNumber);
  307. if (hstu || isPreIdentified)
  308. {
  309. item.IsPreIdentified = true;
  310. item.Status = AuditStatus.APPROVED;
  311. }
  312. ////item.Attachments = new List<AttachmentItem>() { new () { FileId = 1, FileName = "a.png", FileExtName = ".png" } }.ToJson();
  313. //var k = new List<AttachmentItem>() { new() { FileId = 1, FileName = "a.png", FileExtName = ".png" } };
  314. //item.Attachments = JSON.Serialize(k);
  315. await item.InsertAsync();
  316. }
  317. /// <summary>
  318. /// 更新监测特殊学生
  319. /// </summary>
  320. /// <param name="input"></param>
  321. /// <returns></returns>
  322. public async Task Update(UpdateExamSpecialStudentInput input)
  323. {
  324. var oitem = await rep.DetachedEntities.FirstOrDefaultAsync(t => t.Id == input.Id) ?? throw Oops.Oh(ErrorCode.E2001);
  325. var examGrade = await examGradeService.GetById(oitem.ExamGradeId);
  326. var schoolClass = await schoolClassService.GetSchoolClass(oitem.SysOrgId, input.SysOrgBranchId, examGrade, input.ClassNumber);
  327. var item = input.Adapt<ExamSpecialStudent>();
  328. item.SchoolClassId = schoolClass.Id;
  329. item.Name = item.Name?.ClearWhitespace();
  330. item.IdNumber = item.IdNumber?.ClearWhitespace();
  331. if ((await rep.Change<SpecialStudent>().DetachedEntities.AnyAsync(t => t.SysOrgId == item.SysOrgId && t.CertificateType == item.CertificateType && t.IdNumber.ToUpper() == item.IdNumber)) ||
  332. (await rep.AnyAsync(t => t.ExamPlanId == oitem.ExamPlanId && t.SysOrgId == item.SysOrgId && t.CertificateType == item.CertificateType && t.IdNumber.ToUpper() == item.IdNumber))
  333. )
  334. {
  335. item.IsPreIdentified = true;
  336. item.Status = AuditStatus.APPROVED;
  337. }
  338. else if (input.IsRejectedReaudit == true && item.Status == AuditStatus.REJECTED)
  339. {
  340. item.Status = AuditStatus.AUDIT;
  341. }
  342. //item.IsPreIdentified = await _rep.Change<SpecialStudent>().DetachedEntities.AnyAsync(t => t.SysOrgId == item.SysOrgId && t.CertificateType == item.CertificateType && t.IdNumber.ToUpper() == item.IdNumber);
  343. await item.UpdateIncludeAsync([
  344. nameof(item.SchoolClassId),
  345. nameof(item.ClassNumber),
  346. nameof(item.Name),
  347. nameof(item.CertificateType),
  348. nameof(item.IdNumber),
  349. nameof(item.Gender),
  350. nameof(item.BirthDate),
  351. nameof(item.StudentNumber),
  352. nameof(item.Remark),
  353. nameof(item.SysOrgBranchId),
  354. nameof(item.ApplyReason),
  355. nameof(item.PatriarchName),
  356. nameof(item.PatriarchTel),
  357. nameof(item.IsPreIdentified),
  358. nameof(item.Status),
  359. ]);
  360. }
  361. /// <summary>
  362. /// 添加特殊学生佐证材料
  363. /// </summary>
  364. /// <param name="input"></param>
  365. /// <returns></returns>
  366. public async Task AddAttachment(AddAttachmentInput input)
  367. {
  368. var item = await rep.FirstOrDefaultAsync(t => t.Id == input.SourceId) ?? throw Oops.Oh(ErrorCode.E2001);
  369. //var attachments = JSON.Deserialize<List<AttachmentItem>>(item.Attachments);
  370. //attachments.Add(input.Adapt<AttachmentItem>());
  371. //item.Attachments = JSON.Serialize(attachments);
  372. item.Attachments = AttachmentUtil.InsertInto(item.Attachments, input.Adapt<AttachmentItem>());
  373. await item.UpdateIncludeAsync(new[] { nameof(item.Attachments) });
  374. }
  375. /// <summary>
  376. /// 删除特殊学生佐证材料
  377. /// </summary>
  378. /// <param name="input"></param>
  379. /// <returns></returns>
  380. public async Task DelAttachment(DeleteAttachmentInput input)
  381. {
  382. var item = await rep.FirstOrDefaultAsync(t => t.Id == input.SourceId) ?? throw Oops.Oh(ErrorCode.E2001);
  383. var attachments = AttachmentUtil.GetList(item.Attachments);
  384. var a = attachments.FirstOrDefault(t => t.FileId == input.FileId);
  385. if (a != null)
  386. {
  387. attachments.Remove(a);
  388. item.Attachments = JSON.Serialize(attachments);
  389. await item.UpdateIncludeAsync(new[] { nameof(item.Attachments) });
  390. await resourceFileService.Del(new() { Id = a.FileId });
  391. if (a.ThumbFileId.HasValue && a.ThumbFileId > 0)
  392. {
  393. await resourceFileService.Del(new() { Id = a.ThumbFileId.Value });
  394. }
  395. }
  396. }
  397. /// <summary>
  398. /// 验证特殊学生佐证材料
  399. /// </summary>
  400. /// <param name="examPlanId"></param>
  401. /// <returns></returns>
  402. public async Task<bool> VerifyAttachment(int examPlanId)
  403. {
  404. var items = await rep.DetachedEntities.Where(t => t.ExamPlanId == examPlanId && t.SysOrgId == CurrentSysUserInfo.SysOrgId && t.IsPreIdentified == false).ProjectToType<ExamSpecialStudentOutput>().ToListAsync();
  405. if (items.Any(t => t.AttachmentList.Count == 0))
  406. {
  407. return false;
  408. }
  409. return true;
  410. }
  411. /// <summary>
  412. /// 删除监测特殊学生
  413. /// </summary>
  414. /// <param name="input"></param>
  415. /// <returns></returns>
  416. public async Task Del(BaseId input)
  417. {
  418. var item = await rep.FirstOrDefaultAsync(t => t.Id == input.Id) ?? throw Oops.Oh(ErrorCode.E2001);
  419. if (item.IsPreIdentified)
  420. {
  421. throw Oops.Oh(ErrorCode.E2006);
  422. }
  423. var attachments = JSON.Deserialize<List<AttachmentItem>>(item.Attachments);
  424. if (attachments != null && attachments.Count != 0)
  425. {
  426. foreach (var attachment in attachments)
  427. {
  428. await resourceFileService.Del(new() { Id = attachment.FileId });
  429. if (attachment.ThumbFileId.HasValue && attachment.ThumbFileId > 0)
  430. {
  431. await resourceFileService.Del(new() { Id = attachment.ThumbFileId.Value });
  432. }
  433. }
  434. }
  435. await item.DeleteAsync();
  436. }
  437. /// <summary>
  438. /// 清空监测特殊学生
  439. /// </summary>
  440. /// <param name="input"></param>
  441. /// <returns></returns>
  442. public async Task Clear(ClearExamSpecialStudentInput input)
  443. {
  444. var orgId = CurrentSysUserInfo.SysOrgId;
  445. await rep.Where(t => t.ExamPlanId == input.ExamPlanId && t.SysOrgId == orgId && t.IsPreIdentified == false).ExecuteDeleteAsync();
  446. }
  447. /// <summary>
  448. /// 导出监测特殊学生上报打印表格
  449. /// </summary>
  450. /// <param name="examPlanId"></param>
  451. /// <returns></returns>
  452. public async Task<byte[]> ExportPrintTable(int examPlanId)
  453. {
  454. var items = await rep.DetachedEntities.Include(t => t.Grade).Include(t => t.SysOrg)
  455. .Where(t => t.ExamPlanId == examPlanId && t.SysOrgId == CurrentSysUserInfo.SysOrgId && t.Status == AuditStatus.AUDIT)
  456. .ToListAsync();
  457. if (items.Count == 0)
  458. {
  459. throw Oops.Oh(ErrorCode.E2001, "待审核特殊学生");
  460. }
  461. // 获取证件类型
  462. var cts = await sysDictDataService.GetListByDictTypeId(304);
  463. var certificateTypes = cts.ToDictionary(x => (CertificateType)x.Value, y => y.Name);
  464. XWPFDocument doc = new();
  465. doc.Document.body.sectPr = new();
  466. var sectPr = doc.Document.body.sectPr;
  467. sectPr.pgSz.orient = ST_PageOrientation.portrait;
  468. sectPr.pgSz.w = 11906;
  469. sectPr.pgSz.h = 16838;
  470. sectPr.pgMar.left = sectPr.pgMar.right = 1080;
  471. sectPr.pgMar.top = sectPr.pgMar.bottom = 1440;
  472. var gradeItems = items.GroupBy(t => t.GradeId).OrderBy(t => t.Key).ToList();
  473. int pi = 0;
  474. foreach (var gitem in gradeItems)
  475. {
  476. var gradeFirst = gitem.First();
  477. // 多于一个年级分多页
  478. if (0 < pi++)
  479. {
  480. doc.CreateParagraph().IsPageBreak = true;
  481. }
  482. // 标题行
  483. XWPFParagraph pHeader = doc.CreateParagraph();
  484. pHeader.Alignment = ParagraphAlignment.CENTER;
  485. pHeader.SpacingLineRule = LineSpacingRule.AUTO;
  486. pHeader.SetSpacingBetween(1.5, LineSpacingRule.AUTO);
  487. var pHeaderRun = pHeader.CreateRun();
  488. pHeaderRun.SetText($"{gradeFirst.SysOrg.FullName.Replace("重庆市", "")}特殊学生明细表({gradeFirst.Grade.Name})");
  489. pHeaderRun.FontSize = 18;
  490. pHeaderRun.IsBold = true;
  491. // 空行
  492. CreateBlanParagraph();
  493. // 签字行
  494. XWPFParagraph pSign = doc.CreateParagraph();
  495. pSign.SpacingLineRule = LineSpacingRule.AUTO;
  496. pSign.SetSpacingBetween(2, LineSpacingRule.AUTO);
  497. var pSignRun = pSign.CreateRun();
  498. pSignRun.SetText("学校盖章: 纪检负责人: 校长签字:");
  499. pSignRun.FontSize = 12;
  500. // 签字行
  501. XWPFParagraph pCount = doc.CreateParagraph();
  502. pCount.SpacingLineRule = LineSpacingRule.AUTO;
  503. pCount.SetSpacingBetween(2, LineSpacingRule.AUTO);
  504. var dt = DateTime.Now;
  505. var pCountRun = pCount.CreateRun();
  506. pCountRun.SetText($"填表人: 联系电话: 特殊学生计({gitem.Count()})人 {dt.Year}年{dt.Month}月{dt.Day}日");
  507. pCountRun.FontSize = 12;
  508. // 空行
  509. CreateBlanParagraph(1);
  510. #region 生成表格
  511. const int COLUMN_COUNT = 7;
  512. XWPFTable table = doc.CreateTable(gitem.Count() + 1, COLUMN_COUNT);
  513. var tableLayout = table.GetCTTbl().tblPr.AddNewTblLayout();
  514. tableLayout.type = ST_TblLayoutType.@fixed;
  515. table.Width = 5080;
  516. table.SetColumnWidth(0, 280);
  517. table.SetColumnWidth(1, 280);
  518. table.SetColumnWidth(2, 540);
  519. table.SetColumnWidth(3, 580);
  520. table.SetColumnWidth(4, 1070);
  521. table.SetColumnWidth(5, 1660);
  522. table.SetColumnWidth(6, 660);
  523. int ri = 0;
  524. table.GetRow(ri).GetCTRow().AddNewTrPr().AddNewTrHeight().val = 480;
  525. table.GetRow(ri).GetCell(0).SetParagraph(SetCellText(table, "序号", ParagraphAlignment.CENTER, true));
  526. table.GetRow(ri).GetCell(1).SetParagraph(SetCellText(table, "班级", ParagraphAlignment.CENTER, true));
  527. table.GetRow(ri).GetCell(2).SetParagraph(SetCellText(table, "学生姓名", ParagraphAlignment.CENTER, true));
  528. table.GetRow(ri).GetCell(3).SetParagraph(SetCellText(table, "证件类型", ParagraphAlignment.CENTER, true));
  529. table.GetRow(ri).GetCell(4).SetParagraph(SetCellText(table, "证件号码", ParagraphAlignment.CENTER, true));
  530. table.GetRow(ri).GetCell(5).SetParagraph(SetCellText(table, "特殊原因", ParagraphAlignment.CENTER, true));
  531. table.GetRow(ri).GetCell(6).SetParagraph(SetCellText(table, "家长电话", ParagraphAlignment.CENTER, true));
  532. for (int ci = 0; ci < COLUMN_COUNT; ci++)
  533. {
  534. table.GetRow(ri).GetCell(ci).GetCTTc().AddNewTcPr().AddNewVAlign().val = ST_VerticalJc.center;
  535. }
  536. int tableRowNumber = 1;
  537. foreach (var item in gitem)
  538. {
  539. ri++;
  540. table.GetRow(ri).GetCTRow().AddNewTrPr().AddNewTrHeight().val = 480;
  541. table.GetRow(ri).GetCell(0).SetParagraph(SetCellText(table, $"{tableRowNumber++}", ParagraphAlignment.CENTER));
  542. table.GetRow(ri).GetCell(1).SetParagraph(SetCellText(table, $"{item.ClassNumber}", ParagraphAlignment.CENTER));
  543. table.GetRow(ri).GetCell(2).SetParagraph(SetCellText(table, item.Name, ParagraphAlignment.CENTER));
  544. table.GetRow(ri).GetCell(3).SetParagraph(SetCellText(table, certificateTypes[item.CertificateType], ParagraphAlignment.CENTER));
  545. table.GetRow(ri).GetCell(4).SetParagraph(SetCellText(table, item.IdNumber, ParagraphAlignment.CENTER));
  546. table.GetRow(ri).GetCell(5).SetParagraph(SetCellText(table, item.ApplyReason, ParagraphAlignment.LEFT));
  547. table.GetRow(ri).GetCell(6).SetParagraph(SetCellText(table, item.PatriarchTel, ParagraphAlignment.CENTER));
  548. for (int ci = 0; ci < COLUMN_COUNT; ci++)
  549. {
  550. table.GetRow(ri).GetCell(ci).GetCTTc().AddNewTcPr().AddNewVAlign().val = ST_VerticalJc.center;
  551. }
  552. }
  553. // 空行
  554. CreateBlanParagraph();
  555. #endregion
  556. }
  557. // 生成流
  558. using MemoryStream ms = new();
  559. doc.Write(ms);
  560. doc.Close();
  561. var retDoc = ms.ToArray();
  562. ms.Close();
  563. return retDoc;
  564. // 创建空行
  565. void CreateBlanParagraph(double lineSpacing = 1.5)
  566. {
  567. var p = doc.CreateParagraph();
  568. p.SpacingLineRule = LineSpacingRule.AUTO;
  569. p.SetSpacingBetween(lineSpacing, LineSpacingRule.AUTO);
  570. p.CreateRun().FontSize = 12;
  571. }
  572. }
  573. #endregion
  574. #region 查询统计
  575. /// <summary>
  576. /// 分页查询监测特殊学生列表
  577. /// </summary>
  578. /// <param name="input"></param>
  579. /// <returns></returns>
  580. public async Task<PageResult<ExamSpecialStudentOutput>> QueryPageList(ExamSpecialStudentPageInput input)
  581. {
  582. var orgId = input.SysOrgId ?? CurrentSysUserInfo.SysOrgId;
  583. var query = GetQueryBase(input);
  584. //query = query.Where(t => t.ExamPlanId == input.ExamPlanId && t.SysOrgId == orgId);
  585. var ret = await query.OrderBy(t => t.GradeId).ThenBy(t => t.ClassNumber).ThenBy(t => t.Id).ProjectToType<ExamSpecialStudentOutput>().ToADPagedListAsync(input.PageIndex, input.PageSize);
  586. return ret;
  587. }
  588. /// <summary>
  589. /// 分页查询监测特殊学生列表
  590. /// </summary>
  591. /// <param name="input"></param>
  592. /// <returns></returns>
  593. public async Task<PageResult<ExamSpecialStudentOutput>> QueryAuditPageList(ExamSpecialStudentPageInput input)
  594. {
  595. var orgId = input.SysOrgId ?? CurrentSysUserInfo.SysOrgId;
  596. var query = GetQueryBase(input);
  597. //query = query.Where(t => t.ExamPlanId == input.ExamPlanId && t.SysOrgId == orgId);
  598. var ret = await query.OrderBy(t => t.GradeId).ThenBy(t => t.ClassNumber).ThenBy(t => t.Id).ProjectToType<ExamSpecialStudentOutput>().ToADPagedListAsync(input.PageIndex, input.PageSize);
  599. return ret;
  600. }
  601. /// <summary>
  602. /// 获取机构班级特殊学生上报人数统计列表
  603. /// </summary>
  604. /// <param name="examPlanId"></param>
  605. /// <param name="sysOrgId"></param>
  606. /// <returns></returns>
  607. public async Task<ExamSpecialStudentCountOutput> GetOrgGradeClassStudentCount(int examPlanId, short? sysOrgId = null)
  608. {
  609. var orgId = sysOrgId ?? CurrentSysUserInfo.SysOrgId;
  610. var items = await rep.DetachedEntities.Where(t => t.ExamPlanId == examPlanId && t.SysOrgId == orgId)
  611. .GroupBy(t => new { t.GradeId, t.ClassNumber })
  612. .Select(t => new
  613. {
  614. t.Key.GradeId,
  615. t.Key.ClassNumber,
  616. t.FirstOrDefault().Grade,
  617. Count = t.Count(),
  618. })
  619. .ToListAsync();
  620. var cols = items.Select(t => t.ClassNumber).Distinct().ToList();
  621. var retItems = items.ToPivotList(c => c.ClassNumber, r => r.GradeId, d => d.Any() ? d.Sum(x => x.Count) : 0);
  622. foreach (var item in retItems)
  623. {
  624. item.Grade = items.FirstOrDefault(t => t.GradeId == item.GradeId).Grade;
  625. item.GradeTotal = items.Where(t => t.GradeId == item.GradeId).Sum(x => x.Count);
  626. }
  627. int total = retItems.Sum(x => x.GradeTotal);
  628. IDictionary<string, object> totalItem = new ExpandoObject();
  629. totalItem.Add("GradeId", 9999);
  630. totalItem.Add("Grade", new { Id = 9999, Name = "合计" });
  631. totalItem.Add("GradeTotal", total);
  632. retItems.Add(totalItem);
  633. return new()
  634. {
  635. ClassNumberList = cols,
  636. Items = retItems,
  637. Total = total,
  638. };
  639. }
  640. /// <summary>
  641. /// 获取状态数量
  642. /// </summary>
  643. /// <returns></returns>
  644. public async Task<List<StatusCount>> QueryStatusCount(ExamSpecialStudentPageInput input)
  645. {
  646. var query = GetQueryBase(input);
  647. if (query == null)
  648. {
  649. return new List<StatusCount>();
  650. }
  651. var counts = await query.GroupBy(t => t.Status).Select(t => new StatusCount { Status = (int)t.Key, Count = t.Count() }).ToListAsync();
  652. return counts;
  653. }
  654. #endregion
  655. public async Task RefreshFileSize()
  656. {
  657. var items = await rep.Entities.ToListAsync();
  658. foreach (var item in items)
  659. {
  660. var ats = AttachmentUtil.GetList(item.Attachments);
  661. foreach (var at in ats)
  662. {
  663. var file = await rep.Change<ResourceFile>().DetachedEntities.FirstOrDefaultAsync(t => t.Id == at.FileId) ?? throw new Exception("fuckfuck");
  664. at.FileSize = file.FileSize;
  665. }
  666. item.Attachments = JSON.Serialize(ats);
  667. }
  668. await rep.UpdateAsync(items);
  669. }
  670. #region 私有方法
  671. /// <summary>
  672. /// 构建查询
  673. /// </summary>
  674. /// <param name="input"></param>
  675. /// <returns></returns>
  676. private IQueryable<ExamSpecialStudent> GetQueryBase(ExamSpecialStudentPageInput input)
  677. {
  678. var orgId = input.SysOrgId ?? CurrentSysUserInfo.SysOrgId;
  679. var name = !string.IsNullOrEmpty(input.Name?.Trim());
  680. var idNumber = !string.IsNullOrEmpty(input.IdNumber?.Trim());
  681. var applyReason = !string.IsNullOrEmpty(input.ApplyReason?.Trim());
  682. var patriarchTel = !string.IsNullOrEmpty(input.PatriarchTel?.Trim());
  683. var studentNumber = !string.IsNullOrEmpty(input.StudentNumber?.Trim());
  684. var query = rep.DetachedEntities.Where(t => t.ExamPlanId == input.ExamPlanId && t.SysOrgId == orgId)
  685. .Where((name, u => EF.Functions.Like(u.Name, $"%{input.Name.Trim()}%")))
  686. .Where((idNumber, u => EF.Functions.Like(u.IdNumber, $"%{input.IdNumber.Trim()}%")))
  687. .Where((studentNumber, u => EF.Functions.Like(u.StudentNumber, $"%{input.StudentNumber.Trim()}%")))
  688. .Where((applyReason, u => EF.Functions.Like(u.ApplyReason, $"%{input.ApplyReason.Trim()}%")))
  689. .Where((patriarchTel, u => EF.Functions.Like(u.PatriarchTel, $"%{input.PatriarchTel.Trim()}%")))
  690. .Where(input.Status.HasValue, t => t.Status == input.Status)
  691. .Where(input.CertificateType.HasValue, t => t.CertificateType == input.CertificateType)
  692. .Where(input.Gender.HasValue, t => t.Gender == input.Gender)
  693. .Where(input.GradeId.HasValue, t => t.GradeId == input.GradeId)
  694. .Where(input.ClassNumber.HasValue, t => t.ClassNumber == input.ClassNumber)
  695. .Where(input.SysOrgBranchId.HasValue, t => t.SchoolClass.SysOrgBranchId == input.SysOrgBranchId);
  696. return query;
  697. }
  698. /// <summary>
  699. /// 设置单元格文本
  700. /// </summary>
  701. /// <param name="table"></param>
  702. /// <param name="text"></param>
  703. /// <param name="align"></param>
  704. /// <param name="isBold"></param>
  705. /// <returns></returns>
  706. private static XWPFParagraph SetCellText(XWPFTable table, string text, ParagraphAlignment align = ParagraphAlignment.CENTER, bool isBold = false)
  707. {
  708. CT_P para = new();
  709. XWPFParagraph pCell = new(para, table.Body)
  710. {
  711. Alignment = align,
  712. VerticalAlignment = TextAlignment.CENTER,
  713. //SpacingLineRule = LineSpacingRule.AUTO,
  714. };
  715. //pCell.setSpacingBetween(1.5, LineSpacingRule.AUTO);
  716. XWPFRun r1 = pCell.CreateRun();
  717. r1.SetText(text);
  718. r1.FontSize = 11;
  719. r1.FontFamily = "宋体";
  720. r1.IsBold = isBold;
  721. //r1c1.SetTextPosition(20);//设置高度
  722. return pCell;
  723. }
  724. /// <summary>
  725. /// 获取当前机构指定监测计划中的特殊学生列表
  726. /// </summary>
  727. /// <param name="examPlanId"></param>
  728. /// <returns></returns>
  729. private async Task<List<ExamSpecialStudent>> GetPreIdentifiedList(int examPlanId)
  730. {
  731. var items = await rep.Where(t => t.ExamPlanId == examPlanId && t.SysOrgId == CurrentSysUserInfo.SysOrgId && t.IsPreIdentified == true).ToListAsync();
  732. return items;
  733. }
  734. #endregion
  735. }