student.js 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661
  1. 'use strict';
  2. const assert = require('assert');
  3. const _ = require('lodash');
  4. const { ObjectId } = require('mongoose').Types;
  5. const { CrudService } = require('naf-framework-mongoose/lib/service');
  6. const { BusinessError, ErrorCode } = require('naf-core').Error;
  7. const moment = require('moment');
  8. class StudentService extends CrudService {
  9. constructor(ctx) {
  10. super(ctx, 'student');
  11. this.model = this.ctx.model.Student;
  12. this.umodel = this.ctx.model.User;
  13. this.tmodel = this.ctx.model.Trainplan;
  14. this.clamodel = this.ctx.model.Class;
  15. this.upmodel = this.ctx.model.Uploadtask;
  16. this.gmodel = this.ctx.model.Group;
  17. this.psmodel = this.ctx.model.Personalscore;
  18. this.gsmodel = this.ctx.model.Groupscore;
  19. this.uqmodel = this.ctx.model.Uploadquestion;
  20. this.scoremodel = this.ctx.model.Score;
  21. this.leavemodel = this.ctx.model.Leave;
  22. this.attendmodel = this.ctx.model.Attendance;
  23. }
  24. async create(data) {
  25. const { name, phone: mobile, gender } = data;
  26. const res = await this.model.create(data);
  27. if (res) {
  28. const obj = {
  29. name,
  30. mobile,
  31. gender,
  32. type: '4',
  33. passwd: '12345678',
  34. uid: res._id,
  35. };
  36. const user = await this.ctx.service.user.create(obj);
  37. }
  38. return res;
  39. }
  40. async delete({ id }) {
  41. // 删除小组中的这个人,作业表,问卷表,评分,考勤,用户表,学生表
  42. await this.gmodel.update(
  43. { 'students.stuid': id },
  44. { $pull: { students: { stuid: id } } }
  45. );
  46. await this.upmodel.deleteMany({ studentid: id });
  47. await this.uqmodel.deleteMany({ studentid: id });
  48. await this.scoremodel.deleteMany({ stuid: id });
  49. await this.attendmodel.deleteMany({ studentid: id });
  50. await this.umodel.deleteOne({ uid: id });
  51. await this.model.deleteOne({ _id: ObjectId(id) });
  52. }
  53. async update({ id }, data) {
  54. const student = await this.model.findByIdAndUpdate(id, data);
  55. if (student) {
  56. const { phone, name } = data;
  57. await this.umodel.findOneAndUpdate({ uid: id }, { mobile: phone, name });
  58. }
  59. return student;
  60. }
  61. // 查询
  62. async query({ name, ...info }, { skip, limit }) {
  63. const query = { ...info };
  64. if (name) {
  65. query.name = { $regex: name };
  66. }
  67. let data = await this.model
  68. .find(query)
  69. .populate([
  70. {
  71. path: 'classid',
  72. model: 'Class',
  73. select: 'name',
  74. },
  75. {
  76. path: 'planid',
  77. model: 'Trainplan',
  78. },
  79. ])
  80. .skip(parseInt(skip))
  81. .limit(parseInt(limit));
  82. data = JSON.parse(JSON.stringify(data));
  83. for (const stu of data) {
  84. const { classid, planid, termid, batchid } = stu;
  85. // 先拿学生
  86. if (classid && _.isObject(classid)) {
  87. const { _id, name } = classid;
  88. if (name) stu.classname = name;
  89. if (_id) stu.classid = _id;
  90. }
  91. // 拼计划信息
  92. if (planid && _.isObject(planid)) {
  93. const { termnum, _id } = planid;
  94. if (termnum && _.isArray(termnum)) {
  95. const t = termnum.find(f => f._id === termid);
  96. if (t) {
  97. const { batchnum, term } = t;
  98. if (term) stu.termname = term;
  99. if (batchnum && _.isArray(batchnum)) {
  100. const b = batchnum.find(f => f._id === batchid);
  101. if (b) {
  102. const { batch } = b;
  103. if (batch) stu.batchname = batch;
  104. }
  105. }
  106. }
  107. }
  108. stu.planid = _id;
  109. }
  110. }
  111. return data;
  112. }
  113. async count({ name, ...info }) {
  114. const query = { ...info };
  115. if (name) {
  116. query.name = { $regex: name };
  117. }
  118. return await this.model.count(query);
  119. }
  120. // 查询
  121. async seek({ termid, type, batchid, skip, limit }) {
  122. const total = await this.model.count({
  123. termid,
  124. type,
  125. batchid,
  126. $or: [{ classid: null }, { classid: '' }],
  127. });
  128. const data = await this.model
  129. .find({
  130. termid,
  131. type,
  132. batchid,
  133. $or: [{ classid: null }, { classid: '' }],
  134. })
  135. .skip(Number(skip))
  136. .limit(Number(limit));
  137. const result = { total, data };
  138. return result;
  139. }
  140. async findbedroom(data) {
  141. const { batchid, classid } = data;
  142. const result = [];
  143. // 如果传的是批次id
  144. if (batchid) {
  145. // 查询该批次下的所有学生
  146. const students = await this.model.find({ batchid });
  147. const bedroomList = new Set();
  148. // 查询该批次的所有寝室号
  149. for (const student of students) {
  150. bedroomList.add(student.bedroom);
  151. }
  152. let studentList = [];
  153. // 查询该批次所有寝室下的学生名单
  154. for (const bedroom of bedroomList) {
  155. const newstudents = await this.model.find({ bedroom });
  156. for (const newstudent of newstudents) {
  157. studentList.push(newstudent.name);
  158. }
  159. result.push({ bedroom, studentList });
  160. studentList = [];
  161. }
  162. }
  163. // 如果传的是班级id
  164. if (classid) {
  165. // 查询该班级所有学生
  166. const students = await this.model.find({ classid });
  167. const bedroomList = new Set();
  168. // 查询该班级所有寝室号
  169. for (const student of students) {
  170. bedroomList.add(student.bedroom);
  171. }
  172. let studentList = [];
  173. // 查询该班级所有寝室的学生名单
  174. for (const bedroom of bedroomList) {
  175. const newstudents = await this.model.find({ bedroom });
  176. for (const newstudent of newstudents) {
  177. // 如果寝室中有非本班级学生(混寝),则过滤掉不予显示
  178. if (newstudent.classid === classid) {
  179. studentList.push(newstudent.name);
  180. }
  181. }
  182. result.push({ bedroom, studentList });
  183. studentList = [];
  184. }
  185. }
  186. return result;
  187. }
  188. async upjob(data) {
  189. const { stuid, job } = data;
  190. const student = await this.model.findById(stuid);
  191. student.job = job;
  192. if (job === '班长' || job === '学委') {
  193. const user = await this.umodel.findOne({ uid: stuid, type: '4' });
  194. const date = await this.ctx.service.util.updatedate();
  195. const openid = user.openid;
  196. const detail = '你已被班主任设置为' + job + ',请及时登录查看';
  197. const remark = '感谢您的使用';
  198. if (openid) {
  199. this.ctx.service.weixin.sendTemplateMsg(
  200. this.ctx.app.config.REVIEW_TEMPLATE_ID,
  201. openid,
  202. '您有一个新的通知',
  203. detail,
  204. date,
  205. remark
  206. );
  207. }
  208. }
  209. return await student.save();
  210. }
  211. // 根据学生id删除班级
  212. async deleteclass(data) {
  213. for (const el of data) {
  214. const student = await this.model.findById(el);
  215. if (student) {
  216. student.classid = '';
  217. await student.save();
  218. }
  219. }
  220. }
  221. // 根据班级id查出班级各个学生的分数
  222. async findscore({ skip, limit, ...info }) {
  223. const { classid } = info;
  224. const total = await this.model.count(info);
  225. const students = await this.model
  226. .find(info)
  227. .skip(Number(skip))
  228. .limit(Number(limit));
  229. const data = [];
  230. const groups = await this.gmodel.find({ classid });
  231. for (const student of students) {
  232. const _student = JSON.parse(JSON.stringify(student));
  233. const group = groups.find(item =>
  234. item.students.find(stuinfo => stuinfo.stuid === _student.id)
  235. );
  236. if (group) {
  237. _student.groupscore = group.score;
  238. }
  239. const tasks = await this.upmodel.find({ studentid: _student.id });
  240. _student.tasks = tasks;
  241. data.push(_student);
  242. }
  243. return { total, data };
  244. }
  245. async findbystuids({ data }) {
  246. const res = [];
  247. for (const stuid of data) {
  248. const stu = await this.model.findById(stuid);
  249. if (stu) res.push(stu);
  250. }
  251. return res;
  252. }
  253. // 根据学生id删除学生
  254. async deletestus(data) {
  255. throw new BusinessError(
  256. ErrorCode.BUSINESS,
  257. '此功能暂不开放,待确定好会出现的以外情况后,再次开放'
  258. );
  259. // for (const id of data) {
  260. // await this.model.deleteOne({ _id: ObjectId(id) });
  261. // await this.umodel.deleteOne({ uid: id });
  262. // await this.upmodel.deleteMany({ studentid: id });
  263. // await this.uqmodel.deleteMany({ studentid: id });
  264. // await this.scoremodel.deleteMany({ stuid: id });
  265. // await this.attendmodel.deleteMany({ studentid: id });
  266. // }
  267. }
  268. // 批量更新寝室号
  269. async updatabedroom(data) {
  270. for (const el of data) {
  271. const student = await this.model.findById(el.id);
  272. if (student) {
  273. student.bedroom = el.bedroom;
  274. await student.save();
  275. }
  276. }
  277. }
  278. /**
  279. * 计算班级的优秀学生
  280. * 规则:班级干部(学生的job!=='普通学生'),检查是否有评优资格(is_fine_status==='0'可以评优),全部评优;
  281. * 普通学生取总成绩前10的人评优(非10位并列时,名额该占用就占用;第10名若有并列,就全都要)
  282. * @param {String} param0 {id=>班级id}
  283. */
  284. async getFineStudent({ id: classid }) {
  285. // 获取班级学生列表
  286. let studentList = await this.model.find({ classid });
  287. // 重置评优,干部全优秀
  288. studentList = studentList.map(i => {
  289. if (i.job.includes('普通')) i.is_fine = '0';
  290. else i.is_fine = '1';
  291. return i;
  292. });
  293. // 初始化后取出不需要算的人,他们就这样,没必要算
  294. const reverseList = studentList.filter(
  295. f => !(f.is_fine !== '2' && f.job.includes('普通'))
  296. );
  297. // 过滤出取消评优资格的学生和干部;干部就是优秀;被取消资格就别凑热闹了
  298. studentList = studentList.filter(
  299. f => f.is_fine !== '2' && f.job.includes('普通')
  300. );
  301. // 获取平时分
  302. let dailyScoreList = await this.psmodel.find({ classid });
  303. // 去重复
  304. dailyScoreList = this.getDailyScore(dailyScoreList);
  305. studentList = this.dealScoreList(dailyScoreList, studentList, 'daily');
  306. // 获取作业分
  307. let taskScoreList = await this.upmodel.find({ classid });
  308. taskScoreList = this.getTaskScore(taskScoreList);
  309. studentList = this.dealScoreList(taskScoreList, studentList, 'task');
  310. // 获取小组分,小组
  311. const groupList = await this.gmodel.find({ classid });
  312. const groupScoreList = await this.gsmodel.find({ classid });
  313. studentList = this.dealGroupScoreList(
  314. groupList,
  315. groupScoreList,
  316. studentList
  317. );
  318. studentList = studentList.map(i => {
  319. i.score = _.round(
  320. (i.daily || 0) + (i.task || 0) + (i.groupscore || 0),
  321. 2
  322. );
  323. return i;
  324. });
  325. studentList = studentList.sort(
  326. (a, b) => (b.score * 1 || 0) - (a.score * 1 || 0)
  327. );
  328. // 排名
  329. // eslint-disable-next-line no-unused-vars
  330. let num = 0;
  331. for (const student of studentList) {
  332. // 先判断是否超过第10位,超过就跳出
  333. if (num >= 10) break;
  334. const { score, is_fine, job, name, _id } = student;
  335. // 最开始初始化过所有人的状态,并且将干部和不能评优的人都过滤出去,所以正常学生应该都是没有评优,如果此处已经是优秀,那么就是和前人同分改的,直接跳过就好
  336. if (is_fine === '1') continue;
  337. // 没有分凑什么热闹
  338. if (!score) continue;
  339. let plus = 1; // 这轮有多少人,到这了,这个人肯定是要改了,所以默认1
  340. // 评优
  341. student.is_fine = '1';
  342. const rlist = studentList.filter(
  343. f => f.score === score && !ObjectId(f._id).equals(_id)
  344. );
  345. // 处理同分的人也都变成is_fine
  346. for (const stud of rlist) {
  347. stud.is_fine = '1';
  348. const sindex = studentList.findIndex(f =>
  349. ObjectId(stud._id).equals(f._id)
  350. );
  351. if (sindex >= 0) {
  352. studentList[sindex] = stud;
  353. plus++;
  354. }
  355. }
  356. // num+plus,算下num
  357. num = num + plus;
  358. }
  359. // 算完的和不用算的合并,提交
  360. const lastList = [ ...studentList, ...reverseList ];
  361. for (const student of lastList) {
  362. // const res = await student.save();
  363. // const { meta, ...data } = student;
  364. const r = await this.model.findByIdAndUpdate(
  365. { _id: ObjectId(student._id) },
  366. { score: student.score, is_fine: student.is_fine }
  367. );
  368. }
  369. }
  370. /**
  371. * 将分数放到学生身上
  372. * @param {Array} scoreList 班级的分数列表
  373. * @param {Array} studentList 学生列表
  374. * @param {String} type 分数类型
  375. */
  376. dealScoreList(scoreList, studentList, type) {
  377. scoreList = JSON.parse(JSON.stringify(scoreList));
  378. studentList = JSON.parse(JSON.stringify(studentList));
  379. scoreList = _.groupBy(scoreList, 'studentid');
  380. const arr = [];
  381. for (let i of studentList) {
  382. const slist = scoreList[i._id];
  383. const obj = {};
  384. if (slist) {
  385. const score = slist.reduce((p, n) => p + (n.score * 1 || 0), 0);
  386. obj[type] = score;
  387. } else {
  388. obj[type] = 0;
  389. }
  390. i = Object.assign(i, obj);
  391. arr.push(i);
  392. }
  393. return arr;
  394. }
  395. /**
  396. * 将 学生所在 组的 团队平均分 + 到学生身上
  397. * @param {Array} groupList 班级的小组的列表
  398. * @param {Array} scoreList 所有小组的分数列表
  399. * @param {Array} studentList 学生列表
  400. */
  401. dealGroupScoreList(groupList, scoreList, studentList) {
  402. scoreList = _.groupBy(scoreList, 'groupid');
  403. // 算出每组的平均分,之后加给学生
  404. groupList = groupList.map(i => {
  405. const { students } = i;
  406. if (students.length > 0) {
  407. const slist = scoreList[i._id];
  408. if (slist) {
  409. i.score = slist.reduce((p, n) => p + (n.score * 1 || 0), 0);
  410. i.score = _.floor(_.divide(i.score, students.length), 2);
  411. }
  412. }
  413. return i;
  414. });
  415. // 每个学生加自己的组的平均分
  416. studentList = studentList.map(i => {
  417. const r = groupList.find(f =>
  418. f.students.find(sf => ObjectId(sf.stuid).equals(i._id))
  419. );
  420. if (r) {
  421. // i.score = _.round((i.score * 1 || 0) + (r.score * 1 || 0), 2);
  422. i.groupscore = r.score * 1 || 0;
  423. } else {
  424. i.groupscore = 0;
  425. }
  426. return i;
  427. });
  428. return studentList;
  429. }
  430. // 平时分去重处理
  431. getDailyScore(list) {
  432. const arr = [];
  433. const mid1 = _.groupBy(list, 'studentid');
  434. const keys = Object.keys(mid1);
  435. for (const key of keys) {
  436. const mid2 = _.uniqBy(mid1[key], 'subid');
  437. arr.push(...mid2);
  438. }
  439. return arr;
  440. }
  441. // 作业分去重处理
  442. getTaskScore(list) {
  443. const arr = [];
  444. const mid1 = _.groupBy(list, 'studentid');
  445. const keys = Object.keys(mid1);
  446. for (const key of keys) {
  447. const mid2 = _.uniqBy(mid1[key], 'lessonid');
  448. arr.push(...mid2);
  449. }
  450. return arr;
  451. }
  452. // 将学生排号
  453. async arrangeNumber({ classid }) {
  454. const studList = await this.model.find({ classid });
  455. let number = 1;
  456. // 查每个学生的编号,如果没有,就给赋上值;有,就给number赋上值,然后继续下一位
  457. for (const stu of studList) {
  458. if (!stu.number) {
  459. if (number * 1 < 10) stu.number = `0${number}`;
  460. else stu.number = number;
  461. await stu.save();
  462. } else {
  463. number = stu.number * 1;
  464. }
  465. number = number * 1 + 1;
  466. }
  467. number = 1;
  468. }
  469. // 导出学生
  470. async exportStudent(body) {
  471. let { model, ...data } = body;
  472. // 整理model
  473. model = model.map(i => {
  474. const { zh, model } = i;
  475. if (zh && model) {
  476. const obj = {};
  477. obj.header = zh;
  478. if (model === 'termid') obj.key = 'termname';
  479. else if (model === 'batchid') obj.key = 'batchname';
  480. else if (model === 'classid') obj.key = 'classname';
  481. else obj.key = model;
  482. obj.width = 20;
  483. return obj;
  484. }
  485. });
  486. model = _.compact(model);
  487. // 请求数据
  488. // 修改条件,termid变成数组了,需要一个一个查出来
  489. const { planid, termid, batchid, classid } = data;
  490. const queryObject = {};
  491. if (planid) queryObject.planid = planid;
  492. if (batchid) queryObject.batchid = batchid;
  493. if (classid) queryObject.classid = classid;
  494. let studentList = [];
  495. for (const t of termid) {
  496. queryObject.termid = t;
  497. const { data: stuList } = await this.query(queryObject);
  498. studentList.push(...stuList);
  499. }
  500. studentList = JSON.parse(JSON.stringify(studentList));
  501. let ids = studentList.map(i => i.classid);
  502. ids = _.uniq(ids);
  503. ids = _.compact(ids);
  504. ids = ids.map(i => ObjectId(i));
  505. const classList = await this.ctx.model.Class.find({ _id: { $in: ids } });
  506. for (const stu of studentList) {
  507. // 转换是否参培
  508. const { classid, isComming } = stu;
  509. if (!classid) continue;
  510. const cla = await this.ctx.service.class.fetch({ id: classid });
  511. if (!cla) continue;
  512. const { startdate } = cla;
  513. if (!startdate) continue;
  514. stu.insurance = moment(startdate).add(1, 'd').format('YYYY-MM-DD');
  515. if (isComming) {
  516. if (isComming === '0') stu.isComming = '否';
  517. if (isComming === '1') stu.isComming = '是';
  518. }
  519. }
  520. let fn = '学生名单';
  521. // 因为是递进下来, batchid和classid并列,并非递进
  522. if (classid) {
  523. // 文件名称: 期, 班
  524. // 班级名上面有直接拽来
  525. const cla = classList.find(f => ObjectId(classid).equals(f._id));
  526. if (cla) {
  527. const { name, termid } = cla;
  528. if (name) fn = `${name.includes('班') ? name : `${name}班`}${fn}`;
  529. if (termid) {
  530. const obj = await this.toGetFn(termid);
  531. if (obj) {
  532. const { term } = obj;
  533. fn = `第${term}期${fn}`;
  534. }
  535. }
  536. }
  537. } else if (batchid) {
  538. // 文件名称,期,批
  539. const tid = _.head(termid);
  540. const obj = await this.toGetFn(tid, batchid);
  541. if (obj) {
  542. const { term, batch } = obj;
  543. if (batch) fn = `第${batch}批${fn}`;
  544. if (term) fn = `第${term}期${fn}`;
  545. }
  546. } else if (termid) {
  547. // 文件名称: 期
  548. if (termid.length === 1) {
  549. const obj = await this.toGetFn(_.head(termid));
  550. if (obj) {
  551. const { term } = obj;
  552. if (term) fn = `第${term}期${fn}`;
  553. }
  554. } else {
  555. let tStr = '';
  556. for (let i = 0; i < termid.length; i++) {
  557. const tid = termid[i];
  558. const obj = await this.toGetFn(tid);
  559. if (obj) {
  560. const { term } = obj;
  561. if (term) {
  562. if (i === 0) {
  563. tStr += `${term}期`;
  564. } else {
  565. tStr += `,${term}期`;
  566. }
  567. }
  568. }
  569. }
  570. fn = `${tStr}${fn}`;
  571. }
  572. } else if (planid) {
  573. // 文件名称:该计划标题
  574. const trainPlan = await this.ctx.model.Trainplan.findById(planid);
  575. if (trainPlan) {
  576. const { title } = trainPlan;
  577. if (title) fn = `${title}${fn}`;
  578. }
  579. }
  580. return await this.ctx.service.util.toExcel(studentList, model, fn);
  581. }
  582. async toGetFn(termid, batchid) {
  583. const trainPlan = await this.ctx.model.Trainplan.findOne({
  584. 'termnum._id': ObjectId(termid),
  585. });
  586. if (!trainPlan) { throw new BusinessError(ErrorCode.DATA_NOT_EXIST, '未找到年度计划信息'); }
  587. const { termnum } = trainPlan;
  588. if (!termnum) {
  589. throw new BusinessError(
  590. ErrorCode.DATA_NOT_EXIST,
  591. '未找到年度计划的期信息'
  592. );
  593. }
  594. const obj = {};
  595. if (termid) {
  596. const term = termnum.id(termid);
  597. if (term) obj.term = term.term;
  598. if (batchid) {
  599. const { batchnum } = term;
  600. const batch = batchnum.id(batchid);
  601. if (batch) obj.batch = batch.batch;
  602. }
  603. }
  604. return obj;
  605. }
  606. // 确认学生打印证书
  607. async printCert({ ids }) {
  608. const res = await this.model.updateMany(
  609. { _id: { $in: ids.map(i => ObjectId(i)) } },
  610. { cert: '1' }
  611. );
  612. return res;
  613. }
  614. // 根据计划id找到学校是否上传学生
  615. async getSchoolStudent({ planid }) {
  616. const res = await this.model.aggregate([
  617. { $match: { planid } },
  618. { $group: { _id: '$schid', sum: { $sum: 1 } } },
  619. { $project: { _id: 0, schid: '$_id', schnum: '$sum' } },
  620. ]);
  621. return res;
  622. }
  623. // excel导出表头设置
  624. metaBx(type) {
  625. const header = [
  626. {
  627. header: '姓名',
  628. key: 'name',
  629. width: 20,
  630. },
  631. ];
  632. return header;
  633. }
  634. }
  635. module.exports = StudentService;