'use strict'; const assert = require('assert'); const _ = require('lodash'); const { ObjectId } = require('mongoose').Types; const { CrudService } = require('naf-framework-mongoose/lib/service'); const { BusinessError, ErrorCode } = require('naf-core').Error; const moment = require('moment'); class StudentService extends CrudService { constructor(ctx) { super(ctx, 'student'); this.model = this.ctx.model.Student; this.umodel = this.ctx.model.User; this.tmodel = this.ctx.model.Trainplan; this.clamodel = this.ctx.model.Class; this.upmodel = this.ctx.model.Uploadtask; this.gmodel = this.ctx.model.Group; this.psmodel = this.ctx.model.Personalscore; this.gsmodel = this.ctx.model.Groupscore; this.uqmodel = this.ctx.model.Uploadquestion; this.scoremodel = this.ctx.model.Score; this.leavemodel = this.ctx.model.Leave; this.attendmodel = this.ctx.model.Attendance; } async create(data) { const { name, phone: mobile, gender } = data; const res = await this.model.create(data); if (res) { const obj = { name, mobile, gender, type: '4', passwd: '12345678', uid: res._id, }; const user = await this.ctx.service.user.create(obj); } return res; } async delete({ id }) { // 删除小组中的这个人,作业表,问卷表,评分,考勤,用户表,学生表 await this.gmodel.update({ 'students.stuid': id }, { $pull: { students: { stuid: id } } }); await this.upmodel.deleteMany({ studentid: id }); await this.uqmodel.deleteMany({ studentid: id }); await this.scoremodel.deleteMany({ stuid: id }); await this.attendmodel.deleteMany({ studentid: id }); await this.umodel.deleteOne({ uid: id }); await this.model.deleteOne({ _id: ObjectId(id) }); } async update({ id }, data) { const student = await this.model.findByIdAndUpdate(id, data); if (student) { const { phone, name } = data; await this.umodel.findOneAndUpdate({ uid: id }, { mobile: phone, name }); } return student; } // 查询 async query({ skip, limit, ...info }) { const total = await this.model.count(info); let res = await this.model .find(info) .skip(Number(skip)) .limit(Number(limit)); res = JSON.parse(JSON.stringify(res)); let termList = res.map(i => i.termid); termList = _.compact(_.uniq(termList)); termList = termList.map(i => ObjectId(i)); const planList = await this.tmodel.find({ 'termnum._id': { $in: termList }, }); let classid = res.map(i => i.classid); classid = _.compact(_.uniq(classid)); classid = classid.map(i => ObjectId(i)); const classList = await this.clamodel.find({ _id: { $in: classid } }); // 整理数据 res = res.map(i => { const { planid, termid, batchid, classid } = i; // 拿出班级名称 const cla = classList.find(f => ObjectId(classid).equals(f._id)); if (cla) { const { name: classname } = cla; i.classname = classname; } const plan = planList.find(f => ObjectId(planid).equals(f._id)); if (plan) { const { termnum } = plan; if (termnum && _.isArray(termnum)) { const termInfo = termnum.id(termid); if (termInfo) { const { term, batchnum } = termInfo; if (term) i.termname = term; if (batchnum && _.isArray(batchnum)) { const batchInfo = batchnum.id(batchid); if (batchInfo) { const { batch } = batchInfo; if (batch) i.batchname = batch; } } } } } return i; }); const result = { total, data: res }; return result; } // 查询 async seek({ termid, type, batchid, skip, limit }) { const total = await this.model.count({ termid, type, batchid, $or: [{ classid: null }, { classid: '' }], }); const data = await this.model .find({ termid, type, batchid, $or: [{ classid: null }, { classid: '' }], }) .skip(Number(skip)) .limit(Number(limit)); const result = { total, data }; return result; } async findbedroom(data) { const { batchid, classid } = data; const result = []; // 如果传的是批次id if (batchid) { // 查询该批次下的所有学生 const students = await this.model.find({ batchid }); const bedroomList = new Set(); // 查询该批次的所有寝室号 for (const student of students) { bedroomList.add(student.bedroom); } let studentList = []; // 查询该批次所有寝室下的学生名单 for (const bedroom of bedroomList) { const newstudents = await this.model.find({ bedroom }); for (const newstudent of newstudents) { studentList.push(newstudent.name); } result.push({ bedroom, studentList }); studentList = []; } } // 如果传的是班级id if (classid) { // 查询该班级所有学生 const students = await this.model.find({ classid }); const bedroomList = new Set(); // 查询该班级所有寝室号 for (const student of students) { bedroomList.add(student.bedroom); } let studentList = []; // 查询该班级所有寝室的学生名单 for (const bedroom of bedroomList) { const newstudents = await this.model.find({ bedroom }); for (const newstudent of newstudents) { // 如果寝室中有非本班级学生(混寝),则过滤掉不予显示 if (newstudent.classid === classid) { studentList.push(newstudent.name); } } result.push({ bedroom, studentList }); studentList = []; } } return result; } async upjob(data) { const { stuid, job } = data; const student = await this.model.findById(stuid); student.job = job; if (job === '班长' || job === '学委') { const user = await this.umodel.findOne({ uid: stuid, type: '4' }); const date = await this.ctx.service.util.updatedate(); const openid = user.openid; const detail = '你已被班主任设置为' + job + ',请及时登录查看'; const remark = '感谢您的使用'; if (openid) { this.ctx.service.weixin.sendTemplateMsg( this.ctx.app.config.REVIEW_TEMPLATE_ID, openid, '您有一个新的通知', detail, date, remark ); } } return await student.save(); } // 根据学生id删除班级 async deleteclass(data) { for (const el of data) { const student = await this.model.findById(el); if (student) { student.classid = ''; await student.save(); } } } // 根据班级id查出班级各个学生的分数 async findscore({ skip, limit, ...info }) { const { classid } = info; const total = await this.model.count(info); const students = await this.model .find(info) .skip(Number(skip)) .limit(Number(limit)); const data = []; const groups = await this.gmodel.find({ classid }); for (const student of students) { const _student = JSON.parse(JSON.stringify(student)); const group = groups.find(item => item.students.find(stuinfo => stuinfo.stuid === _student.id) ); if (group) { _student.groupscore = group.score; } const tasks = await this.upmodel.find({ studentid: _student.id }); _student.tasks = tasks; data.push(_student); } return { total, data }; } async findbystuids({ data }) { const res = []; for (const stuid of data) { const stu = await this.model.findById(stuid); if (stu) res.push(stu); } return res; } // 根据学生id删除学生 async deletestus(data) { throw new BusinessError( ErrorCode.BUSINESS, '此功能暂不开放,待确定好会出现的以外情况后,再次开放' ); // for (const id of data) { // await this.model.deleteOne({ _id: ObjectId(id) }); // await this.umodel.deleteOne({ uid: id }); // await this.upmodel.deleteMany({ studentid: id }); // await this.uqmodel.deleteMany({ studentid: id }); // await this.scoremodel.deleteMany({ stuid: id }); // await this.attendmodel.deleteMany({ studentid: id }); // } } // 批量更新寝室号 async updatabedroom(data) { for (const el of data) { const student = await this.model.findById(el.id); if (student) { student.bedroom = el.bedroom; await student.save(); } } } /** * 计算班级的优秀学生 * 规则:班级干部(学生的job!=='普通学生'),检查是否有评优资格(is_fine_status==='0'可以评优),全部评优; * 普通学生取总成绩前10的人评优(非10位并列时,名额该占用就占用;第10名若有并列,就全都要) * @param {String} param0 {id=>班级id} */ async getFineStudent({ id: classid }) { // 获取班级学生列表 let studentList = await this.model.find({ classid }); // 重置评优,干部全优秀 studentList = studentList.map(i => { if (i.job.includes('普通')) i.is_fine = '0'; else i.is_fine = '1'; return i; }); // 初始化后取出不需要算的人,他们就这样,没必要算 const reverseList = studentList.filter( f => !(f.is_fine !== '2' && f.job.includes('普通')) ); // 过滤出取消评优资格的学生和干部;干部就是优秀;被取消资格就别凑热闹了 studentList = studentList.filter( f => f.is_fine !== '2' && f.job.includes('普通') ); // 获取平时分 let dailyScoreList = await this.psmodel.find({ classid }); // 去重复 dailyScoreList = this.getDailyScore(dailyScoreList); studentList = this.dealScoreList(dailyScoreList, studentList, 'daily'); // 获取作业分 let taskScoreList = await this.upmodel.find({ classid }); taskScoreList = this.getTaskScore(taskScoreList); studentList = this.dealScoreList(taskScoreList, studentList, 'task'); // 获取小组分,小组 const groupList = await this.gmodel.find({ classid }); const groupScoreList = await this.gsmodel.find({ classid }); studentList = this.dealGroupScoreList( groupList, groupScoreList, studentList ); studentList = studentList.map(i => { i.score = _.round((i.daily || 0) + (i.task || 0) + (i.groupscore || 0), 2); return i; }); studentList = studentList.sort( (a, b) => (b.score * 1 || 0) - (a.score * 1 || 0) ); // 排名 // eslint-disable-next-line no-unused-vars let num = 0; for (const student of studentList) { // 先判断是否超过第10位,超过就跳出 if (num >= 10) break; const { score, is_fine, job, name, _id } = student; // 最开始初始化过所有人的状态,并且将干部和不能评优的人都过滤出去,所以正常学生应该都是没有评优,如果此处已经是优秀,那么就是和前人同分改的,直接跳过就好 if (is_fine === '1') continue; // 没有分凑什么热闹 if (!score) continue; let plus = 1; // 这轮有多少人,到这了,这个人肯定是要改了,所以默认1 // 评优 student.is_fine = '1'; const rlist = studentList.filter(f => f.score === score && !ObjectId(f._id).equals(_id)); // 处理同分的人也都变成is_fine for (const stud of rlist) { stud.is_fine = '1'; const sindex = studentList.findIndex(f => ObjectId(stud._id).equals(f._id) ); if (sindex >= 0) { studentList[sindex] = stud; plus++; } } // num+plus,算下num num = num + plus; } // 算完的和不用算的合并,提交 const lastList = [ ...studentList, ...reverseList ]; for (const student of lastList) { // const res = await student.save(); // const { meta, ...data } = student; const r = await this.model.findByIdAndUpdate( { _id: ObjectId(student._id) }, { score: student.score, is_fine: student.is_fine } ); } } /** * 将分数放到学生身上 * @param {Array} scoreList 班级的分数列表 * @param {Array} studentList 学生列表 * @param {String} type 分数类型 */ dealScoreList(scoreList, studentList, type) { scoreList = JSON.parse(JSON.stringify(scoreList)); studentList = JSON.parse(JSON.stringify(studentList)); scoreList = _.groupBy(scoreList, 'studentid'); const arr = []; for (let i of studentList) { const slist = scoreList[i._id]; const obj = {}; if (slist) { const score = slist.reduce((p, n) => p + (n.score * 1 || 0), 0); obj[type] = score; } else { obj[type] = 0; } i = Object.assign(i, obj); arr.push(i); } return arr; } /** * 将 学生所在 组的 团队平均分 + 到学生身上 * @param {Array} groupList 班级的小组的列表 * @param {Array} scoreList 所有小组的分数列表 * @param {Array} studentList 学生列表 */ dealGroupScoreList(groupList, scoreList, studentList) { scoreList = _.groupBy(scoreList, 'groupid'); // 算出每组的平均分,之后加给学生 groupList = groupList.map(i => { const { students } = i; if (students.length > 0) { const slist = scoreList[i._id]; if (slist) { i.score = slist.reduce((p, n) => p + (n.score * 1 || 0), 0); i.score = _.floor(_.divide(i.score, students.length), 2); } } return i; }); // 每个学生加自己的组的平均分 studentList = studentList.map(i => { const r = groupList.find(f => f.students.find(sf => ObjectId(sf.stuid).equals(i._id)) ); if (r) { // i.score = _.round((i.score * 1 || 0) + (r.score * 1 || 0), 2); i.groupscore = r.score * 1 || 0; } else { i.groupscore = 0; } return i; }); return studentList; } // 平时分去重处理 getDailyScore(list) { const arr = []; const mid1 = _.groupBy(list, 'studentid'); const keys = Object.keys(mid1); for (const key of keys) { const mid2 = _.uniqBy(mid1[key], 'subid'); arr.push(...mid2); } return arr; } // 作业分去重处理 getTaskScore(list) { const arr = []; const mid1 = _.groupBy(list, 'studentid'); const keys = Object.keys(mid1); for (const key of keys) { const mid2 = _.uniqBy(mid1[key], 'lessonid'); arr.push(...mid2); } return arr; } // 将学生排号 async arrangeNumber({ classid }) { const studList = await this.model.find({ classid }); let number = 1; // 查每个学生的编号,如果没有,就给赋上值;有,就给number赋上值,然后继续下一位 for (const stu of studList) { if (!stu.number) { if (number * 1 < 10) stu.number = `0${number}`; else stu.number = number; await stu.save(); } else { number = stu.number * 1; } number = number * 1 + 1; } number = 1; } // 导出学生 目前:拓展训练保险用 async exportStudent(body) { let { model, ...data } = body; // 整理model model = model.map(i => { const { zh, model } = i; if (zh && model) { const obj = {}; obj.header = zh; if (model === 'termid') obj.key = 'termname'; else if (model === 'batchid') obj.key = 'batchname'; else if (model === 'classid') obj.key = 'classname'; else obj.key = model; obj.width = 20; return obj; } }); model = _.compact(model); // 请求数据 let { data: studentList } = await this.query(data); studentList = JSON.parse(JSON.stringify(studentList)); let ids = studentList.map(i => i.classid); ids = _.uniq(ids); ids = _.compact(ids); ids = ids.map(i => ObjectId(i)); const classList = await this.ctx.model.Class.find({ _id: { $in: ids } }); for (const stu of studentList) { const { classid } = stu; if (!classid) continue; const cla = await this.ctx.service.class.fetch({ id: classid }); if (!cla) continue; const { startdate } = cla; if (!startdate) continue; stu.insurance = moment(startdate).add(1, 'd').format('YYYY-MM-DD'); } let fn = '学生名单'; // 因为是递进下来, batchid和classid并列,并非递进 const { planid, termid, batchid, classid } = data; const trainPlanInfo = async (termid, batchid) => { const trainPlan = await this.ctx.model.Trainplan.findOne({ 'termnum._id': ObjectId(termid) }); if (!trainPlan) throw new BusinessError(ErrorCode.DATA_NOT_EXIST, '未找到年度计划信息'); const { termnum } = trainPlan; if (!termnum) throw new BusinessError(ErrorCode.DATA_NOT_EXIST, '未找到年度计划的期信息'); const obj = {}; if (termid) { const term = termnum.id(termid); if (term) obj.term = term.term; if (batchid) { const { batchnum } = term; const batch = batchnum.id(batchid); if (batch) obj.batch = batch.batch; } } return obj; }; if (classid) { // 文件名称: 期, 班 // 班级名上面有直接拽来 const cla = classList.find(f => ObjectId(classid).equals(f._id)); if (cla) { const { name, termid } = cla; if (name) fn = `${name.includes('班') ? name : `${name}班`}${fn}`; if (termid) { const obj = await trainPlanInfo(termid); if (obj) { const { term } = obj; fn = `第${term}期${fn}`; } } } } else if (batchid) { // 文件名称,期,批 const obj = await trainPlanInfo(termid, batchid); if (obj) { const { term, batch } = obj; if (batch) fn = `第${batch}批${fn}`; if (term) fn = `第${term}期${fn}`; } } else if (termid) { // 文件名称: 期 const obj = await trainPlanInfo(termid); if (obj) { const { term } = obj; if (term) fn = `第${term}期${fn}`; } } else if (planid) { // 文件名称:该计划标题 const trainPlan = await this.ctx.model.Trainplan.findById(planid); if (trainPlan) { const { title } = trainPlan; if (title) fn = `${title}${fn}`; } } return await this.ctx.service.util.toExcel(studentList, model, fn); } // excel导出表头设置 metaBx(type) { const header = [ { header: '姓名', key: 'name', width: 20, }, ]; return header; } } module.exports = StudentService;