student.js 19 KB

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