student.js 17 KB

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