ExamAbsentReplaceService.cs 37 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832
  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 partial class ExamAbsentReplaceService : IExamAbsentReplaceService, ITransient
  13. {
  14. private readonly IRepository<ExamAbsentReplace> _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 ExamAbsentReplaceService(IRepository<ExamAbsentReplace> 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<UploadExamAbsentReplaceOutput>> Upload(string filePath, int examPlanId)
  37. {
  38. UploadExamDataOutput<UploadExamAbsentReplaceOutput> 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 EXAM_NUM_INDEX = index++;
  62. int REASON_INDEX = index++;
  63. int COURSES_INDEX = index++;
  64. int TEL_INDEX = index++;
  65. int REPLACE_NAME_INDEX = index++;
  66. int REPLACE_EXAM_NUM_INDEX = index++;
  67. int REMARK_INDEX = index;
  68. Dictionary<int, string> headers = new()
  69. {
  70. { GRADE_INDEX, "年级号" },
  71. { CLASS_INDEX, "班级号" },
  72. { NAME_INDEX, "缺测学生姓名" },
  73. { EXAM_NUM_INDEX, "缺测学生监测号" },
  74. { REASON_INDEX, "缺测原因" },
  75. { COURSES_INDEX, "缺测科目" },
  76. { TEL_INDEX, "缺测学生家长电话" },
  77. { REPLACE_NAME_INDEX, "替补学生姓名" },
  78. { REPLACE_EXAM_NUM_INDEX, "替补学生监测号" },
  79. { REMARK_INDEX, "备注" },
  80. };
  81. List<string> headerErrors = new();
  82. for (int i = 0; i <= index; i++)
  83. {
  84. if (headerRow.GetCell(i)?.ToString() != headers[i])
  85. {
  86. char letter = (char)('A' + i);
  87. headerErrors.Add(letter.ToString());
  88. }
  89. }
  90. if (headerErrors.Any())
  91. {
  92. string columnErrors = string.Join("、", headerErrors);
  93. result.ErrorMessage.Add($"第2行标题行{columnErrors}列名错误。从A列开始依次应为年级号、班级号、缺测学生姓名、缺测学生监测号、缺测原因、缺测科目、家长电话、替补学生姓名、替补学生监测号和备注。");
  94. return result;
  95. }
  96. result.StructureCorrect = true;
  97. #endregion
  98. #region 处理数据
  99. // 监测年级
  100. var examGrades = await _examGradeService.GetListByExamPlanId(examPlanId);
  101. // 科目列表
  102. var courses = await _courseService.GetAllLiteList();
  103. // 科目字典
  104. var courseDicts = courses.ToDictionary(t => t.Name);
  105. // 读取数据
  106. List<UploadExamAbsentReplaceOutput> data = new();
  107. int rn = 0;
  108. while (rows.MoveNext())
  109. {
  110. IRow row = (IRow)rows.Current;
  111. string rv = row.GetCell(0)?.ToString().Trim() ?? "";
  112. rv += row.GetCell(1)?.ToString().Trim() ?? "";
  113. if (rv == "")
  114. {
  115. break;
  116. }
  117. UploadExamAbsentReplaceOutput item = new() { RowNumber = ++rn };
  118. // 年级
  119. if (short.TryParse(row.GetCell(GRADE_INDEX)?.ToString(), out short gradeNumber))
  120. {
  121. var g = examGrades.FirstOrDefault(t => t.Grade.GradeNumber == gradeNumber);
  122. if (g == null)
  123. {
  124. item.ErrorMessage.Add($"{headers[GRADE_INDEX]}与监测年级不符");
  125. }
  126. else
  127. {
  128. item.ExamGradeId = g.Id;
  129. item.GradeId = g.GradeId;
  130. }
  131. }
  132. else
  133. {
  134. item.ErrorMessage.Add(headers[CLASS_INDEX]);
  135. }
  136. // 班级
  137. if (short.TryParse(row.GetCell(CLASS_INDEX)?.ToString(), out short classNumber))
  138. {
  139. item.ClassNumber = classNumber;
  140. if (item.ClassNumber < 1 || item.ClassNumber > 35)
  141. {
  142. item.ErrorMessage.Add($"{headers[CLASS_INDEX]}超限");
  143. }
  144. }
  145. else
  146. {
  147. item.ErrorMessage.Add(headers[CLASS_INDEX]);
  148. }
  149. // 缺测学生姓名
  150. item.AbsentName = (row.GetCell(NAME_INDEX)?.ToString() ?? "").ClearWhitespace();
  151. if (item.AbsentName == "" || item.AbsentName.Length > 100)
  152. {
  153. item.ErrorMessage.Add($"{headers[NAME_INDEX]}未填或超出100字");
  154. }
  155. if (item.AbsentName.Length > 100) { item.AbsentName = item.AbsentName[..100]; }
  156. // 缺测学生监测号
  157. item.AbsentExamNumber = (row.GetCell(EXAM_NUM_INDEX)?.ToString() ?? "").ClearWhitespace().ToUpper();
  158. if (item.AbsentExamNumber == "" || item.AbsentExamNumber.Length > 20)
  159. {
  160. item.ErrorMessage.Add($"{headers[EXAM_NUM_INDEX]}格式错误");
  161. }
  162. if (item.AbsentExamNumber.Length > 20) { item.AbsentExamNumber = item.AbsentExamNumber[..20]; }
  163. // 缺测原因
  164. item.AbsentReason = (row.GetCell(REASON_INDEX)?.ToString() ?? "").ClearWhitespace();
  165. if (item.AbsentReason == "" || item.AbsentReason.Length > 200)
  166. {
  167. item.ErrorMessage.Add($"{headers[REASON_INDEX]}未填或超出200字");
  168. }
  169. if (item.AbsentReason.Length > 200) { item.AbsentReason = item.AbsentReason[..200]; }
  170. // 缺测科目
  171. item.AbsentCourseText = (row.GetCell(COURSES_INDEX)?.ToString() ?? "").ClearWhitespace();
  172. if (item.AbsentCourseText == "")
  173. {
  174. item.ErrorMessage.Add($"{headers[COURSES_INDEX]}未填");
  175. }
  176. else
  177. {
  178. var acourses = GetCourses(courseDicts, item.AbsentCourseText);
  179. item.AbsentCourseList = acourses.courses;
  180. if (acourses.errorMessage.Count > 0)
  181. {
  182. item.ErrorMessage.Add($"{headers[COURSES_INDEX]}({string.Join("、", acourses.errorMessage)})名称错误");
  183. }
  184. }
  185. // 缺测学生家长电话
  186. item.PatriarchTel = (row.GetCell(TEL_INDEX)?.ToString() ?? "").ClearWhitespace();
  187. if (item.PatriarchTel == "" || item.PatriarchTel.Length > 50)
  188. {
  189. item.ErrorMessage.Add($"{headers[TEL_INDEX]}未填或超出50字");
  190. }
  191. if (item.PatriarchTel.Length > 50) { item.PatriarchTel = item.PatriarchTel[..50]; }
  192. // 替补学生姓名
  193. item.ReplaceName = (row.GetCell(REPLACE_NAME_INDEX)?.ToString() ?? "").ClearWhitespace();
  194. if (item.ReplaceName.Length > 100) { item.ReplaceName = item.ReplaceName[..100]; }
  195. // 替补学生监测号
  196. item.ReplaceExamNumber = (row.GetCell(REPLACE_EXAM_NUM_INDEX)?.ToString() ?? "").ClearWhitespace().ToUpper();
  197. if (item.ReplaceExamNumber.Length > 20) { item.ReplaceExamNumber = item.ReplaceExamNumber[..20]; }
  198. if (item.ReplaceName.Length == 0 && item.ReplaceExamNumber.Length != 0 || item.ReplaceName.Length != 0 && item.ReplaceExamNumber.Length == 0)
  199. {
  200. item.ErrorMessage.Add($"有替补时替补学生姓名和监测号必须都填写");
  201. }
  202. // 备注
  203. item.Remark = (row.GetCell(REMARK_INDEX)?.ToString() ?? "").ClearWhitespace();
  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(ImportExamAbsentReplaceInput 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.AbsentExamNumber).Distinct().ToListAsync() ?? new List<string>();
  238. List<ExamAbsentReplace> items = new();
  239. var spStus = input.Items.Where(t => !existsIdNumberList.Contains(t.AbsentExamNumber)).ToList();
  240. var gs = spStus.Select(t => t.ExamGradeId).Distinct().ToList();
  241. var examGrades = await _rep.Change<ExamGrade>().DetachedEntities.Where(t => gs.Contains(t.Id)).Select(t => t.Adapt<ExamGradeOutput>()).ToListAsync();
  242. int c = 0;
  243. foreach (var eg in examGrades)
  244. {
  245. var classNumbers = spStus.Where(t => t.ExamGradeId == eg.Id).Select(t => t.ClassNumber).Distinct().ToList();
  246. var classDict = await _schoolClassService.GetImportSchoolClassList(new()
  247. {
  248. SysOrgId = orgId,
  249. SysOrgBranchId = input.SysOrgBranchId,
  250. ExamGrade = eg,
  251. ClassNumberList = classNumbers,
  252. });
  253. var citems = spStus.Where(t => t.ExamGradeId == eg.Id).ToList();
  254. foreach (var ni in citems)
  255. {
  256. var item = ni.Adapt<ExamAbsentReplace>();
  257. item.ExamPlanId = input.ExamPlanId;
  258. item.SysOrgId = orgId;
  259. item.SysOrgBranchId = input.SysOrgBranchId;
  260. item.SchoolClassId = classDict[ni.ClassNumber];
  261. item.AbsentName = item.AbsentName?.ClearWhitespace();
  262. item.AbsentExamNumber = item.AbsentExamNumber?.ClearWhitespace();
  263. item.ReplaceName = item.ReplaceName?.ClearWhitespace();
  264. item.ReplaceExamNumber = item.ReplaceExamNumber?.ClearWhitespace();
  265. items.Add(item);
  266. c++;
  267. }
  268. }
  269. await _rep.InsertAsync(items);
  270. return c;
  271. }
  272. #endregion
  273. #region 创建编辑
  274. /// <summary>
  275. /// 添加监测缺测替补
  276. /// </summary>
  277. /// <param name="input"></param>
  278. /// <returns></returns>
  279. public async Task Add(AddExamAbsentReplaceInput input)
  280. {
  281. var orgId = CurrentSysUserInfo.SysOrgId;
  282. // 检测同一监测计划中同机构内是否有相同监测号的学生
  283. var sameItems = await _rep.DetachedEntities.Where(t => t.ExamPlanId == input.ExamPlanId && t.SysOrgId == orgId && t.AbsentExamNumber.ToUpper() == input.AbsentExamNumber.ToUpper()).ProjectToType<ExamAbsentReplaceOutput>().ToListAsync();
  284. if (sameItems.Any())
  285. {
  286. throw Oops.Oh(ErrorCode.E2003, string.Join("、", sameItems.Select(t => $"{t.ExamGrade.Grade.Name}{t.ClassNumber}班{t.AbsentName}")), "监测号");
  287. }
  288. var examGrade = await _examGradeService.GetById(input.ExamGradeId);
  289. var schoolClass = await _schoolClassService.GetSchoolClass(orgId, input.SysOrgBranchId, examGrade, input.ClassNumber);
  290. var item = input.Adapt<ExamAbsentReplace>();
  291. item.SysOrgId = orgId;
  292. item.SchoolClassId = schoolClass.Id;
  293. item.AbsentName = item.AbsentName?.ClearWhitespace();
  294. item.AbsentExamNumber = item.AbsentExamNumber?.ClearWhitespace();
  295. item.ReplaceName = item.ReplaceName?.ClearWhitespace();
  296. item.ReplaceExamNumber = item.ReplaceExamNumber?.ClearWhitespace();
  297. await item.InsertAsync();
  298. }
  299. /// <summary>
  300. /// 更新监测缺测替补
  301. /// </summary>
  302. /// <param name="input"></param>
  303. /// <returns></returns>
  304. public async Task Update(UpdateExamAbsentReplaceInput input)
  305. {
  306. var oitem = await _rep.DetachedEntities.FirstOrDefaultAsync(t => t.Id == input.Id) ?? throw Oops.Oh(ErrorCode.E2001);
  307. var examGrade = await _examGradeService.GetById(oitem.ExamGradeId);
  308. var schoolClass = await _schoolClassService.GetSchoolClass(oitem.SysOrgId, input.SysOrgBranchId, examGrade, input.ClassNumber);
  309. var item = input.Adapt<ExamAbsentReplace>();
  310. item.SchoolClassId = schoolClass.Id;
  311. item.AbsentName = item.AbsentName?.ClearWhitespace();
  312. item.AbsentExamNumber = item.AbsentExamNumber?.ClearWhitespace();
  313. item.ReplaceName = item.ReplaceName?.ClearWhitespace();
  314. item.ReplaceExamNumber = item.ReplaceExamNumber?.ClearWhitespace();
  315. await item.UpdateIncludeAsync(new[] {
  316. nameof(item.SysOrgBranchId),
  317. nameof(item.SchoolClassId),
  318. nameof(item.ClassNumber),
  319. nameof(item.AbsentName),
  320. nameof(item.AbsentExamNumber),
  321. nameof(item.AbsentReason),
  322. nameof(item.AbsentCourses),
  323. nameof(item.IsReplaced),
  324. nameof(item.ReplaceName),
  325. nameof(item.ReplaceExamNumber),
  326. nameof(item.Remark),
  327. nameof(item.PatriarchName),
  328. nameof(item.PatriarchTel),
  329. });
  330. }
  331. /// <summary>
  332. /// 添加缺测替补佐证材料
  333. /// </summary>
  334. /// <param name="input"></param>
  335. /// <returns></returns>
  336. public async Task AddAttachment(AddAttachmentInput input)
  337. {
  338. var item = await _rep.FirstOrDefaultAsync(t => t.Id == input.SourceId) ?? throw Oops.Oh(ErrorCode.E2001);
  339. item.Attachments = AttachmentUtil.InsertInto(item.Attachments, input.Adapt<AttachmentItem>());
  340. await item.UpdateIncludeAsync(new[] { nameof(item.Attachments) });
  341. }
  342. /// <summary>
  343. /// 删除缺测替补佐证材料
  344. /// </summary>
  345. /// <param name="input"></param>
  346. /// <returns></returns>
  347. public async Task DelAttachment(DeleteAttachmentInput input)
  348. {
  349. var item = await _rep.FirstOrDefaultAsync(t => t.Id == input.SourceId) ?? throw Oops.Oh(ErrorCode.E2001);
  350. var attachments = AttachmentUtil.GetList(item.Attachments);
  351. var a = attachments.FirstOrDefault(t => t.FileId == input.FileId);
  352. if (a != null)
  353. {
  354. attachments.Remove(a);
  355. item.Attachments = JSON.Serialize(attachments);
  356. await item.UpdateIncludeAsync(new[] { nameof(item.Attachments) });
  357. await _resourceFileService.Del(new() { Id = a.FileId });
  358. if (a.ThumbFileId.HasValue && a.ThumbFileId > 0)
  359. {
  360. await _resourceFileService.Del(new() { Id = a.ThumbFileId.Value });
  361. }
  362. }
  363. }
  364. /// <summary>
  365. /// 验证缺测替补佐证材料
  366. /// </summary>
  367. /// <param name="examPlanId"></param>
  368. /// <returns></returns>
  369. public async Task<bool> VerifyAttachment(int examPlanId)
  370. {
  371. var items = await _rep.DetachedEntities.Where(t => t.ExamPlanId == examPlanId && t.SysOrgId == CurrentSysUserInfo.SysOrgId).ProjectToType<ExamAbsentReplaceOutput>().ToListAsync();
  372. if (items.Any(t => t.AttachmentList.Count == 0))
  373. {
  374. return false;
  375. }
  376. return true;
  377. }
  378. /// <summary>
  379. /// 删除监测缺测替补
  380. /// </summary>
  381. /// <param name="input"></param>
  382. /// <returns></returns>
  383. public async Task Del(BaseId input)
  384. {
  385. var item = await _rep.FirstOrDefaultAsync(t => t.Id == input.Id) ?? throw Oops.Oh(ErrorCode.E2001);
  386. var attachments = JSON.Deserialize<List<AttachmentItem>>(item.Attachments);
  387. if (attachments != null && attachments.Any())
  388. {
  389. foreach (var attachment in attachments)
  390. {
  391. await _resourceFileService.Del(new() { Id = attachment.FileId });
  392. if (attachment.ThumbFileId.HasValue && attachment.ThumbFileId > 0)
  393. {
  394. await _resourceFileService.Del(new() { Id = attachment.ThumbFileId.Value });
  395. }
  396. }
  397. }
  398. await item.DeleteAsync();
  399. }
  400. /// <summary>
  401. /// 清空监测缺测替补
  402. /// </summary>
  403. /// <param name="input"></param>
  404. /// <returns></returns>
  405. public async Task Clear(ClearExamAbsentReplaceInput input)
  406. {
  407. var orgId = CurrentSysUserInfo.SysOrgId;
  408. await _rep.Where(t => t.ExamPlanId == input.ExamPlanId && t.SysOrgId == orgId).ExecuteDeleteAsync();
  409. }
  410. /// <summary>
  411. /// 导出监测缺测替补上报打印表格
  412. /// </summary>
  413. /// <param name="examPlanId"></param>
  414. /// <returns></returns>
  415. public async Task<byte[]> ExportPrintTable(int examPlanId)
  416. {
  417. var items = await _rep.DetachedEntities.Include(t => t.Grade).Include(t => t.SysOrg).Where(t => t.ExamPlanId == examPlanId && t.SysOrgId == CurrentSysUserInfo.SysOrgId).ToListAsync();
  418. if (!items.Any())
  419. {
  420. throw Oops.Oh(ErrorCode.E2001);
  421. }
  422. // 获取证件类型
  423. var cts = await _sysDictDataService.GetListByDictTypeId(304);
  424. var certificateTypes = cts.ToDictionary(x => (CertificateType)x.Value, y => y.Name);
  425. XWPFDocument doc = new();
  426. doc.Document.body.sectPr = new();
  427. var sectPr = doc.Document.body.sectPr;
  428. sectPr.pgSz.orient = ST_PageOrientation.landscape;
  429. sectPr.pgSz.w = 16838;
  430. sectPr.pgSz.h = 11906;
  431. sectPr.pgMar.left = sectPr.pgMar.right = 1080;
  432. sectPr.pgMar.top = sectPr.pgMar.bottom = 1440;
  433. var gradeItems = items.GroupBy(t => t.GradeId).OrderBy(t => t.Key).ToList();
  434. int pi = 0;
  435. foreach (var gitem in gradeItems)
  436. {
  437. var gradeFirst = gitem.First();
  438. // 多于一个年级分多页
  439. if (0 < pi++)
  440. {
  441. doc.CreateParagraph().IsPageBreak = true;
  442. }
  443. // 标题行
  444. XWPFParagraph pHeader = doc.CreateParagraph();
  445. pHeader.Alignment = ParagraphAlignment.CENTER;
  446. pHeader.SpacingLineRule = LineSpacingRule.AUTO;
  447. pHeader.SetSpacingBetween(1.5, LineSpacingRule.AUTO);
  448. var pHeaderRun = pHeader.CreateRun();
  449. pHeaderRun.SetText($"{gradeFirst.SysOrg.FullName.Replace("重庆市", "").Replace("重庆", "")}缺测替补明细表({gradeFirst.Grade.Name})");
  450. pHeaderRun.FontSize = 18;
  451. pHeaderRun.IsBold = true;
  452. // 空行
  453. CreateBlanParagraph();
  454. // 签字行
  455. XWPFParagraph pSign = doc.CreateParagraph();
  456. pSign.SpacingLineRule = LineSpacingRule.AUTO;
  457. pSign.SetSpacingBetween(2, LineSpacingRule.AUTO);
  458. var pSignRun = pSign.CreateRun();
  459. pSignRun.SetText("学校盖章: 纪检负责人: 校长签字:");
  460. pSignRun.FontSize = 12;
  461. // 签字行
  462. XWPFParagraph pCount = doc.CreateParagraph();
  463. pCount.SpacingLineRule = LineSpacingRule.AUTO;
  464. pCount.SetSpacingBetween(2, LineSpacingRule.AUTO);
  465. var dt = DateTime.Now;
  466. var pCountRun = pCount.CreateRun();
  467. pCountRun.SetText($"填表人: 联系电话: 督考签字: 缺测替补计({gitem.Count()})人 {dt.Year}年{dt.Month}月{dt.Day}日");
  468. pCountRun.FontSize = 12;
  469. // 空行
  470. CreateBlanParagraph(1);
  471. #region 生成表格
  472. const int COLUMN_COUNT = 10;
  473. XWPFTable table = doc.CreateTable(gitem.Count() + 2, COLUMN_COUNT);
  474. var tableLayout = table.GetCTTbl().tblPr.AddNewTblLayout();
  475. tableLayout.type = ST_TblLayoutType.@fixed;
  476. table.Width = 5080;
  477. table.SetColumnWidth(0, 280);
  478. table.SetColumnWidth(1, 280);
  479. table.SetColumnWidth(2, 540);
  480. table.SetColumnWidth(3, 640);
  481. table.SetColumnWidth(4, 1000);
  482. table.SetColumnWidth(5, 1200);
  483. table.SetColumnWidth(6, 660);
  484. table.SetColumnWidth(7, 540);
  485. table.SetColumnWidth(8, 640);
  486. table.SetColumnWidth(9, 660);
  487. int ri = 0;
  488. table.GetRow(ri).GetCTRow().AddNewTrPr().AddNewTrHeight().val = 480;
  489. table.GetRow(ri).GetCell(0).SetParagraph(SetCellText(table, "序号", ParagraphAlignment.CENTER, true));
  490. table.GetRow(ri).GetCell(1).SetParagraph(SetCellText(table, "班级", ParagraphAlignment.CENTER, true));
  491. table.GetRow(ri).GetCell(2).SetParagraph(SetCellText(table, "学生姓名", ParagraphAlignment.CENTER, true));
  492. table.GetRow(ri).GetCell(3).SetParagraph(SetCellText(table, "监测号", ParagraphAlignment.CENTER, true));
  493. table.GetRow(ri).GetCell(4).SetParagraph(SetCellText(table, "缺测原因", ParagraphAlignment.CENTER, true));
  494. table.GetRow(ri).GetCell(5).SetParagraph(SetCellText(table, "缺测科目", ParagraphAlignment.CENTER, true));
  495. table.GetRow(ri).GetCell(6).SetParagraph(SetCellText(table, "家长电话", ParagraphAlignment.CENTER, true));
  496. table.GetRow(ri).GetCell(7).SetParagraph(SetCellText(table, "有替补学生", ParagraphAlignment.CENTER, true));
  497. table.GetRow(ri).GetCell(8).SetParagraph(SetCellText(table, null, ParagraphAlignment.CENTER, true));
  498. table.GetRow(ri).GetCell(9).SetParagraph(SetCellText(table, "备注", ParagraphAlignment.CENTER, true));
  499. for (int ci = 0; ci < COLUMN_COUNT; ci++)
  500. {
  501. table.GetRow(ri).GetCell(ci).GetCTTc().AddNewTcPr().AddNewVAlign().val = ST_VerticalJc.center;
  502. }
  503. ri++;
  504. table.GetRow(ri).GetCTRow().AddNewTrPr().AddNewTrHeight().val = 480;
  505. table.GetRow(ri).GetCell(7).SetParagraph(SetCellText(table, "姓名", ParagraphAlignment.CENTER, true));
  506. table.GetRow(ri).GetCell(7).GetCTTc().AddNewTcPr().AddNewVAlign().val = ST_VerticalJc.center;
  507. table.GetRow(ri).GetCell(8).SetParagraph(SetCellText(table, "监测号", ParagraphAlignment.CENTER, true));
  508. table.GetRow(ri).GetCell(8).GetCTTc().AddNewTcPr().AddNewVAlign().val = ST_VerticalJc.center;
  509. MergeCells(table, 9, 9, ri - 1, ri);
  510. MergeCells(table, 7, 8, ri - 1, ri - 1);
  511. for (int i = 0; i < 7; i++)
  512. {
  513. MergeCells(table, i, i, ri - 1, ri);
  514. }
  515. int tableRowNumber = 1;
  516. foreach (var item in gitem)
  517. {
  518. var courses = JSON.Deserialize<List<CourseMiniOutput>>(item.AbsentCourses);
  519. var courseText = string.Join("、", courses.Select(t => t.Name));
  520. ri++;
  521. table.GetRow(ri).GetCTRow().AddNewTrPr().AddNewTrHeight().val = 480;
  522. table.GetRow(ri).GetCell(0).SetParagraph(SetCellText(table, $"{tableRowNumber++}", ParagraphAlignment.CENTER));
  523. table.GetRow(ri).GetCell(1).SetParagraph(SetCellText(table, $"{item.ClassNumber}", ParagraphAlignment.CENTER));
  524. table.GetRow(ri).GetCell(2).SetParagraph(SetCellText(table, item.AbsentName, ParagraphAlignment.CENTER));
  525. table.GetRow(ri).GetCell(3).SetParagraph(SetCellText(table, item.AbsentExamNumber, ParagraphAlignment.CENTER));
  526. table.GetRow(ri).GetCell(4).SetParagraph(SetCellText(table, item.AbsentReason, ParagraphAlignment.LEFT));
  527. table.GetRow(ri).GetCell(5).SetParagraph(SetCellText(table, courseText, ParagraphAlignment.LEFT));
  528. table.GetRow(ri).GetCell(6).SetParagraph(SetCellText(table, item.PatriarchTel, ParagraphAlignment.CENTER));
  529. table.GetRow(ri).GetCell(7).SetParagraph(SetCellText(table, item.ReplaceName, ParagraphAlignment.CENTER));
  530. table.GetRow(ri).GetCell(8).SetParagraph(SetCellText(table, item.ReplaceExamNumber, ParagraphAlignment.CENTER));
  531. table.GetRow(ri).GetCell(9).SetParagraph(SetCellText(table, item.Remark, ParagraphAlignment.LEFT));
  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. }
  537. // 空行
  538. CreateBlanParagraph();
  539. #endregion
  540. }
  541. // 生成流
  542. using MemoryStream ms = new();
  543. doc.Write(ms);
  544. doc.Close();
  545. var retDoc = ms.ToArray();
  546. ms.Close();
  547. return retDoc;
  548. // 创建空行
  549. void CreateBlanParagraph(double lineSpacing = 1.5)
  550. {
  551. var p = doc.CreateParagraph();
  552. p.SpacingLineRule = LineSpacingRule.AUTO;
  553. p.SetSpacingBetween(lineSpacing, LineSpacingRule.AUTO);
  554. p.CreateRun().FontSize = 12;
  555. }
  556. }
  557. #endregion
  558. #region 查询统计
  559. /// <summary>
  560. /// 分页查询监测缺测替补列表
  561. /// </summary>
  562. /// <param name="input"></param>
  563. /// <returns></returns>
  564. public async Task<PageResult<ExamAbsentReplaceOutput>> QueryPageList(ExamAbsentReplacePageInput input)
  565. {
  566. var orgId = input.SysOrgId ?? CurrentSysUserInfo.SysOrgId;
  567. var query = GetQueryBase(input);
  568. var ret = await query.OrderBy(t => t.GradeId).ThenBy(t => t.ClassNumber).ThenBy(t => t.Id)
  569. .ProjectToType<ExamAbsentReplaceOutput>()
  570. .ToADPagedListAsync(input.PageIndex, input.PageSize);
  571. return ret;
  572. }
  573. /// <summary>
  574. /// 分页查询监测缺测替补列表
  575. /// </summary>
  576. /// <param name="input"></param>
  577. /// <returns></returns>
  578. public async Task<PageResult<ExamAbsentReplaceOutput>> QueryAuditPageList(ExamAbsentReplacePageInput input)
  579. {
  580. var orgId = input.SysOrgId ?? CurrentSysUserInfo.SysOrgId;
  581. var query = GetQueryBase(input);
  582. var ret = await query.OrderBy(t => t.GradeId).ThenBy(t => t.ClassNumber).ThenBy(t => t.Id)
  583. .ProjectToType<ExamAbsentReplaceOutput>()
  584. .ToADPagedListAsync(input.PageIndex, input.PageSize);
  585. return ret;
  586. }
  587. /// <summary>
  588. /// 获取机构班级缺测替补上报人数统计列表
  589. /// </summary>
  590. /// <param name="examPlanId"></param>
  591. /// <param name="sysOrgId"></param>
  592. /// <returns></returns>
  593. public async Task<ExamAbsentReplaceCountOutput> GetOrgGradeClassStudentCount(int examPlanId, short? sysOrgId = null)
  594. {
  595. var orgId = sysOrgId ?? CurrentSysUserInfo.SysOrgId;
  596. var items = await _rep.DetachedEntities.Where(t => t.ExamPlanId == examPlanId && t.SysOrgId == orgId)
  597. .GroupBy(t => new { t.GradeId, t.ClassNumber })
  598. .Select(t => new
  599. {
  600. t.Key.GradeId,
  601. t.Key.ClassNumber,
  602. t.FirstOrDefault().Grade,
  603. Count = t.Count(),
  604. })
  605. .ToListAsync();
  606. var cols = items.Select(t => t.ClassNumber).Distinct().ToList();
  607. var retItems = items.ToPivotList(c => c.ClassNumber, r => r.GradeId, d => d.Any() ? d.Sum(x => x.Count) : 0);
  608. foreach (var item in retItems)
  609. {
  610. item.Grade = items.FirstOrDefault(t => t.GradeId == item.GradeId).Grade;
  611. item.GradeTotal = items.Where(t => t.GradeId == item.GradeId).Sum(x => x.Count);
  612. }
  613. int total = retItems.Sum(x => x.GradeTotal);
  614. IDictionary<string, object> totalItem = new ExpandoObject();
  615. totalItem.Add("GradeId", 9999);
  616. totalItem.Add("Grade", new { Id = 9999, Name = "合计" });
  617. totalItem.Add("GradeTotal", total);
  618. retItems.Add(totalItem);
  619. return new()
  620. {
  621. ClassNumberList = cols,
  622. Items = retItems,
  623. Total = total,
  624. };
  625. }
  626. /// <summary>
  627. /// 获取状态数量
  628. /// </summary>
  629. /// <returns></returns>
  630. public async Task<List<StatusCount>> QueryStatusCount(ExamAbsentReplacePageInput input)
  631. {
  632. var query = GetQueryBase(input);
  633. if (query == null)
  634. {
  635. return new List<StatusCount>();
  636. }
  637. var counts = await query.GroupBy(t => t.Status).Select(t => new StatusCount { Status = (int)t.Key, Count = t.Count() }).ToListAsync();
  638. return counts;
  639. }
  640. #endregion
  641. #region 私有方法
  642. /// <summary>
  643. /// 构建查询
  644. /// </summary>
  645. /// <param name="input"></param>
  646. /// <returns></returns>
  647. private IQueryable<ExamAbsentReplace> GetQueryBase(ExamAbsentReplacePageInput input)
  648. {
  649. var orgId = input.SysOrgId ?? CurrentSysUserInfo.SysOrgId;
  650. var absentName = !string.IsNullOrEmpty(input.AbsentName?.Trim());
  651. var absentExamNumber = !string.IsNullOrEmpty(input.AbsentExamNumber?.Trim());
  652. var absentReason = !string.IsNullOrEmpty(input.AbsentReason?.Trim());
  653. var replaceName = !string.IsNullOrEmpty(input.ReplaceName?.Trim());
  654. var replaceExamNumber = !string.IsNullOrEmpty(input.ReplaceExamNumber?.Trim());
  655. var patriarchTel = !string.IsNullOrEmpty(input.PatriarchTel?.Trim());
  656. var remark = !string.IsNullOrEmpty(input.Remark?.Trim());
  657. var query = _rep.DetachedEntities.Where(t => t.ExamPlanId == input.ExamPlanId && t.SysOrgId == orgId)
  658. .Where((absentName, u => EF.Functions.Like(u.AbsentName, $"%{input.AbsentName.Trim()}%")))
  659. .Where((absentExamNumber, u => EF.Functions.Like(u.AbsentExamNumber, $"%{input.AbsentExamNumber.Trim()}%")))
  660. .Where((absentReason, u => EF.Functions.Like(u.AbsentReason, $"%{input.AbsentReason.Trim()}%")))
  661. .Where((replaceName, u => EF.Functions.Like(u.ReplaceName, $"%{input.ReplaceName.Trim()}%")))
  662. .Where((replaceExamNumber, u => EF.Functions.Like(u.ReplaceExamNumber, $"%{input.ReplaceExamNumber.Trim()}%")))
  663. .Where((patriarchTel, u => EF.Functions.Like(u.PatriarchTel, $"%{input.PatriarchTel.Trim()}%")))
  664. .Where((remark, u => EF.Functions.Like(u.Remark, $"%{input.Remark.Trim()}%")))
  665. .Where((input.AbentCourseId.HasValue, u => EF.Functions.Like(u.AbsentCourses, $"%{input.AbentCourseId}%")))
  666. .Where(input.Status.HasValue, t => t.Status == input.Status)
  667. .Where(input.IsReplaced.HasValue, t => t.IsReplaced == input.IsReplaced)
  668. .Where(input.GradeId.HasValue, t => t.GradeId == input.GradeId)
  669. .Where(input.ClassNumber.HasValue, t => t.ClassNumber == input.ClassNumber)
  670. .Where(input.SysOrgBranchId.HasValue, t => t.SchoolClass.SysOrgBranchId == input.SysOrgBranchId);
  671. return query;
  672. }
  673. /// <summary>
  674. /// 根据科目字符串组合获取科目列表
  675. /// </summary>
  676. /// <param name="courseDict">科目字典</param>
  677. /// <param name="courses">科目组合</param>
  678. /// <returns></returns>
  679. private static (List<string> errorMessage, List<CourseMiniOutput> courses) GetCourses(Dictionary<string, CourseLiteOutput> courseDict, string courses)
  680. {
  681. List<CourseMiniOutput> ret = [];
  682. List<string> errs = [];
  683. var cns = courses.ClearWhitespace().Split(['、', ',', ',', ';', ';']);
  684. foreach (var cn in cns)
  685. {
  686. if (courseDict.TryGetValue(cn, out CourseLiteOutput c))
  687. {
  688. if (ret.Any(t => t.Id != c.Id))
  689. {
  690. ret.Add(c.Adapt<CourseMiniOutput>());
  691. }
  692. }
  693. else
  694. {
  695. errs.Add(cn);
  696. }
  697. }
  698. return (errs, ret);
  699. }
  700. /// <summary>
  701. /// 设置单元格文本
  702. /// </summary>
  703. /// <param name="table"></param>
  704. /// <param name="text"></param>
  705. /// <param name="align"></param>
  706. /// <param name="isBold"></param>
  707. /// <returns></returns>
  708. private static XWPFParagraph SetCellText(XWPFTable table, string text, ParagraphAlignment align = ParagraphAlignment.CENTER, bool isBold = false)
  709. {
  710. CT_P para = new();
  711. XWPFParagraph pCell = new(para, table.Body)
  712. {
  713. Alignment = align,
  714. VerticalAlignment = TextAlignment.CENTER,
  715. };
  716. XWPFRun r1 = pCell.CreateRun();
  717. r1.SetText(text);
  718. r1.FontSize = 11;
  719. r1.FontFamily = "宋体";
  720. r1.IsBold = isBold;
  721. return pCell;
  722. }
  723. /// <summary>
  724. /// 合并单元格
  725. /// </summary>
  726. /// <param name="table"></param>
  727. /// <param name="fromCol"></param>
  728. /// <param name="toCol"></param>
  729. /// <param name="fromRow"></param>
  730. /// <param name="toRow"></param>
  731. /// <returns></returns>
  732. private static XWPFTableCell MergeCells(XWPFTable table, int fromCol, int toCol, int fromRow, int toRow)
  733. {
  734. for (int rowIndex = fromRow; rowIndex <= toRow; rowIndex++)
  735. {
  736. if (fromCol < toCol)
  737. {
  738. table.GetRow(rowIndex).MergeCells(fromCol, toCol);
  739. }
  740. XWPFTableCell rowcell = table.GetRow(rowIndex).GetCell(fromCol);
  741. CT_Tc cttc = rowcell.GetCTTc();
  742. if (cttc.tcPr == null)
  743. {
  744. cttc.AddNewTcPr();
  745. }
  746. if (rowIndex == fromRow)
  747. {
  748. // The first merged cell is set with RESTART merge value
  749. rowcell.GetCTTc().tcPr.AddNewVMerge().val = ST_Merge.restart;
  750. }
  751. else
  752. {
  753. // Cells which join (merge) the first one, are set with CONTINUE
  754. rowcell.GetCTTc().tcPr.AddNewVMerge().val = ST_Merge.@continue;
  755. }
  756. }
  757. table.GetRow(fromRow).GetCell(fromCol).SetVerticalAlignment(XWPFTableCell.XWPFVertAlign.CENTER);
  758. //table.GetRow(fromRow).GetCell(fromCol).Paragraphs[0].Alignment = ParagraphAlignment.CENTER;
  759. table.GetRow(fromRow).GetCell(fromCol).Paragraphs[0].Alignment = ParagraphAlignment.LEFT;
  760. return table.GetRow(fromRow).GetCell(fromCol);
  761. }
  762. #endregion
  763. }