ExamSpecialStudentService.cs 33 KB

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