'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; class ClassService extends CrudService { constructor(ctx) { super(ctx, 'class'); this.model = this.ctx.model.Class; this.stumodel = this.ctx.model.Student; this.lessmodel = this.ctx.model.Lesson; this.umodel = this.ctx.model.User; this.tmodel = this.ctx.model.Trainplan; this.gmodel = this.ctx.model.Group; this.heamodel = this.ctx.model.Headteacher; this.teamodel = this.ctx.model.Teacher; this.locamodel = this.ctx.model.Location; } async divide(data) { const { planid, termid } = data; assert(planid, '计划id为必填项'); assert(termid, '期id为必填项'); // 先自动生成班级 TODO:之后放开 await this.autoclass(planid, termid); // 根据计划id与期id查询所有批次下的班级 const newclass = await this.model.find({ planid, termid }); if (!newclass) { throw new BusinessError(ErrorCode.DATA_NOT_EXIST, '班级信息不存在'); } // 根据计划和期查询所有上报的学生 并按照学校排序 const newstudent = await this.stumodel.find({ termid }).sort({ schid: 1 }); if (!newstudent) { throw new BusinessError(ErrorCode.DATA_NOT_EXIST, '学生信息不存在'); } // 按批次分组,每个批次处理自己的 const claGroup = _.groupBy(newclass, 'batchid'); const keys = Object.keys(claGroup); const result = {}; // key:班级id;value:学生id数组 for (const bkey of keys) { const classList = claGroup[bkey]; // 该批次下的班级列表 const batchStudentList = _.shuffle(newstudent.filter(f => f.batchid === bkey)); // shuffle:打乱顺序 该批次下的学生列表 // 分为多个学校 男/女 数组 // 按学校分组 const sgroup = _.groupBy(batchStudentList, 'schid'); // 学校代码key const schKeys = Object.keys(sgroup); const studentGroup = {}; // key:学校id-boys/girls;value:对应学校,性别的学生列表 for (const skey of schKeys) { const schstus = sgroup[skey]; const boys = schstus.filter(f => f.gender.includes('男')); const girls = schstus.filter(f => f.gender.includes('女')); studentGroup[`${skey}-boy`] = boys; studentGroup[`${skey}-girl`] = girls; } // 往班级里塞人 for (const cla of classList) { let { number, _id, name } = cla; result[_id] = []; number = parseInt(number); if (!_.isNumber(number)) throw new BusinessError(ErrorCode.DATA_INVALID, `${name}班的人数设置无法转换成数字处理`); const ko = { boy: 0, girl: 0 }; // key:学校/性别; value:分配多少人了 // 制作keyList,主要是把学校计数key放进去 for (const skey of schKeys) ko[skey] = 0; // 循环这个班的坑:目的就是给班级的坑填上 for (let i = 0; i < number; i++) { // 通过方法,获取备选 学校 性别的选择数组 const selects = this.getSelects(ko); // 循环优先级结果,依次验证,如果成立,则直接按照schid和gender去找对应学校的对应性别的学生,推进去;删掉学生;计数 for (const s of selects) { const { schid, gender } = s; // 获取数组 let stuList = _.cloneDeep(_.get(studentGroup, `${schid}-${gender}`, [])); // 有学生,则为最优解 const head = _.head(stuList); if (head) { // 推人,删除,计数 const { _id: studentid } = head; // 推人 result[_id].push(studentid); // 删人 stuList = _.drop(stuList); studentGroup[`${schid}-${gender}`] = stuList; // 计数 ko[schid]++; ko[gender]++; break; } else { // 下一个选择验证 continue; } } } } } // 更新学生的班级 const ckeys = Object.keys(result); for (const classid of ckeys) { await this.stumodel.updateMany({ _id: result[classid] }, { classid }); } // 新添,给学生排序号 const claList = await this.model.find({ termid }); for (const cla of claList) { await this.ctx.service.student.arrangeNumber({ classid: cla._id }); } } // 获取选择结果 getSelects(keyObject) { const keys = Object.keys(keyObject); const skeys = keys.filter(f => !_.isNaN(parseInt(f))); // 学校计数器的key const gkeys = keys.filter(f => _.isNaN(parseInt(f))); // 性别计数器的key // 得到学校计数的升序排列(数最少的在最上面) let sr = []; for (const skey of skeys) { sr.push({ key: skey, value: keyObject[skey] }); } sr = _.orderBy(sr, [ 'value' ], [ 'asc' ]); // 同理获得性别计数的升序排列 let gr = []; for (const gkey of gkeys) { gr.push({ key: gkey, value: keyObject[gkey] }); } gr = _.orderBy(gr, [ 'value' ], [ 'asc' ]); // 最后双循环得到最后学校+性别的优先级结果 const result = []; // 优先性别 for (const g of gr) { for (const s of sr) { result.push({ schid: s.key, gender: g.key }); } } return result; } // 取得同样类型的学生 async getstutype(_students, type) { const data = []; for (const stuid of _students) { const student = await this.stumodel.findById(stuid); if (student && student.type === type) { data.push(stuid); } } return data; } // 自动生成班级私有方法 async autoclass(planid, termid) { // 删除所有计划下的班级 await this.model.deleteMany({ planid, termid }); // 根据批次id取得当前批次具体信息 const res = await this.tmodel.findById(planid); if (!res) { throw new BusinessError(ErrorCode.DATA_NOT_EXIST, '全年计划信息不存在'); } // 循环出所有班级进行添加操作 const term = await res.termnum.id(termid); for (const batch of term.batchnum) { const classs = await batch.class; for (const cla of classs) { const newdata = { name: cla.name, number: cla.number, batchid: batch.id, termid: term.id, planid: res.id, type: cla.type }; const rescla = await this.model.create(newdata); await this.toSetClassSetting({ classid: rescla._id }); } } } // 根据传入的学生列表和班级id更新学生信息 async studentup(classid, batchid, beforestu) { // 循环学生id for (const stuid of beforestu) { const student = await this.stumodel.findById(stuid); if (student) { student.classid = classid; student.batchid = batchid; await student.save(); } } } // 自动分组 async groupcreate(termid, batchid, classid) { const group = await this.gmodel.find({ termid, batchid, classid }); if (group.length === 0) { for (let i = 1; i < 8; i++) { const name = i + '组'; const newdata = { name, termid, batchid, classid }; await this.gmodel.create(newdata); } } } // 根据传入的学生列表和班级id更新学生信息 async studentupclass({ id }, data) { assert(id, '班级id为必填项'); // 根据全年计划表id查出对应的全年计划详细信息 const trainplan = await this.tmodel.findOne({ 'termnum.batchnum.class._id': ObjectId(id) }); if (!trainplan) { throw new BusinessError(ErrorCode.DATA_NOT_EXIST, '全年计划信息不存在'); } // 取得计划期批次信息 let termid = ''; let batchid = ''; let classname = ''; let class_ = {}; for (const term of trainplan.termnum) { for (const batch of term.batchnum) { const _class = await batch.class.id(id); if (_class) { termid = term.id; batchid = batch.id; classname = _class.name; class_ = _class; break; } } } if (!class_) { throw new BusinessError(ErrorCode.DATA_NOT_EXIST, '班级信息不存在'); } let classid_ = ''; if (classname) { const cla_ = await this.model.findOne({ termid, batchid, name: classname }); if (cla_) { classid_ = cla_.id; } else { const newdata = { name: class_.name, number: class_.number, batchid, termid, planid: trainplan.id, type: class_.type, headteacherid: class_.headteacherid, }; const rescla = await this.model.create(newdata); if (rescla) { classid_ = rescla.id; } } } if (classid_) { // 循环学生id for (const stuid of data) { const student = await this.stumodel.findById(stuid); if (student) { student.classid = classid_; await student.save(); } } } // 添加,给学生排序号 await this.ctx.service.student.arrangeNumber({ classid: classid_ }); // TODO 根据模板复制班级信息 await this.toSetClassSetting({ classid: classid_ }); } async notice(data) { for (const classid of data.classids) { // 根据班级id找到需要通知的班级 const _class = await this.model.findById(classid); const { headteacherid } = _class; // 根据班级id找到对应的课程表 const lesson = await this.lessmodel.findOne({ classid }); if (lesson) { const lessons = lesson.lessons; const remark = '感谢您的使用'; const date = await this.ctx.service.util.updatedate(); const detail = '班级各项信息已确认,请注意查收'; // 遍历班级授课教师发送通知 for (const lessoninfo of lessons) { const teaid = lessoninfo.teaid; const _teacher = await this.umodel.findOne({ uid: teaid, type: '3' }); if (_teacher) { const teaopenid = _teacher.openid; this.ctx.service.weixin.sendTemplateMsg(this.ctx.app.config.REVIEW_TEMPLATE_ID, teaopenid, '您有一个新的通知', detail, date, remark, classid); } } // 给班主任发送通知 const _headteacher = await this.umodel.findOne({ uid: headteacherid, type: '1' }); if (_headteacher) { const headteaopenid = _headteacher.openid; this.ctx.service.weixin.sendTemplateMsg(this.ctx.app.config.REVIEW_TEMPLATE_ID, headteaopenid, '您有一个新的通知', detail, date, remark, classid); } // 根据班级的期id查询对应的培训计划 const trainplan = await this.tmodel.findOne({ 'termnum._id': _class.termid }); const term = await trainplan.termnum.id(_class.termid); const batch = await term.batchnum.id(_class.batchid); const startdate = batch.startdate; const classname = _class.name; // 给班级所有学生发送邮件通知 const students = await this.stumodel.find({ classid }); for (const student of students) { const { email, name } = student; const subject = '吉林省高等学校毕业生就业指导中心通知'; const text = name + '您好!\n欢迎参加由吉林省高等学校毕业生就业指导中心举办的“双困生培训会”。\n您所在的班级为:' + classname + '\n班级开课时间为:' + startdate; this.ctx.service.util.sendMail(email, subject, text); } } } } async uptea(data) { for (const _data of data) { const classInfo = await this.model.findById(_data.id); classInfo.headteacherid = _data.headteacherid; await classInfo.save(); } } async query({ skip, limit, ...info }) { const classes = await this.model .find(info) .populate([ { path: 'yclocationid', model: 'Location', select: 'name', }, { path: 'kzjhlocationid', model: 'Location', select: 'name', }, { path: 'kbyslocationid', model: 'Location', select: 'name', }, { path: 'jslocationid', model: 'Location', select: 'name', }, { path: 'headteacherid', model: 'Headteacher', select: 'name', }, ]) .skip(Number(skip)) .limit(Number(limit)); const data = []; let planids = classes.map(i => i.planid); planids = _.uniq(planids); const trainplan = await this.tmodel.find({ _id: { $in: planids } }); for (const _class of classes) { let res = await this.setClassData(_class, trainplan); if (res) { res = this.setData(res); data.push(res); } else { data.push(_class); } } return data; } async fetch({ id }) { let classInfo = await this.model.findById(id).populate([ { path: 'yclocationid', model: 'Location', select: 'name', }, { path: 'kzjhlocationid', model: 'Location', select: 'name', }, { path: 'kbyslocationid', model: 'Location', select: 'name', }, { path: 'jslocationid', model: 'Location', select: 'name', }, { path: 'headteacherid', model: 'Headteacher', select: 'name', }, ]); const trainplan = await this.tmodel.findById(classInfo.planid); classInfo = await this.setClassData(classInfo, [ trainplan ]); classInfo = this.setData(classInfo); return classInfo; } // 整理数据,找礼仪教师 async setClassData(cla, trainplan) { const { planid, termid, batchid } = cla; cla = JSON.parse(JSON.stringify(cla)); const tpRes = trainplan.find(f => ObjectId(planid).equals(f._id)); if (!tpRes) throw new BusinessError(ErrorCode.DATA_NOT_EXIST, '未找到班级的计划信息'); const t = tpRes.termnum.id(termid); if (!t) throw new BusinessError(ErrorCode.DATA_NOT_EXIST, '未找到班级的期信息'); const { term, batchnum } = t; if (!term) throw new BusinessError(ErrorCode.DATA_NOT_EXIST, '未找到班级的期信息'); else cla.term = term; if (!batchnum) throw new BusinessError(ErrorCode.DATA_NOT_EXIST, '未找到班级的批次信息'); const b = batchnum.id(batchid); if (!b) throw new BusinessError(ErrorCode.DATA_NOT_EXIST, '未找到班级的批次信息'); const { batch, startdate, enddate } = b; if (batch)cla.batch = batch; if (startdate) cla.startdate = startdate; if (enddate) cla.enddate = enddate; // 礼仪教师 if (cla.lyteacherid) { let res = await this.teamodel.findById(cla.lyteacherid); if (!res) res = await this.heamodel.findById(cla.lyteacherid); if (res) cla.lyteacher = res.name; } return cla; } // 整理数据 setData(cla) { const { headteacherid, yclocationid, kzjhlocationid, kbyslocationid, jslocationid } = cla; const arr = []; if (headteacherid && _.isObject(headteacherid)) arr.push({ headteacherid }); if (yclocationid && _.isObject(yclocationid)) arr.push({ yclocationid }); if (kzjhlocationid && _.isObject(kzjhlocationid)) arr.push({ kzjhlocationid }); if (kbyslocationid && _.isObject(kbyslocationid)) arr.push({ kbyslocationid }); if (jslocationid && _.isObject(jslocationid)) arr.push({ jslocationid }); for (const kid of arr) { for (const key in kid) { if (kid.hasOwnProperty(key)) { const obj = kid[key]; const { _id, name } = obj; const keynoids = key.split('id'); cla[key] = _id; cla[_.get(keynoids, 0)] = name; } } } return cla; } async upclasses(data) { for (const _data of data) { await this.model.findByIdAndUpdate(_data.id, _data); } } async classinfo({ id: classid }) { const _classes = await this.model.findById(classid); // 班级信息 const classes = _.cloneDeep(JSON.parse(JSON.stringify(_classes))); // 学生信息 const students = await this.stumodel.find({ classid }); // 所有用户信息 const users = await this.umodel.find(); if (students) { for (const stu of students) { const user = users.find(item => item.uid === stu.id); if (user && user.openid) { const _stu = _.cloneDeep(JSON.parse(JSON.stringify(stu))); _stu.hasuserinfo = '1'; _.remove(students, stu); students.push(_stu); } } classes.students = students; } // 班主任信息 let headteacher; if (classes.headteacherid) { headteacher = await this.heamodel.findById(classes.headteacherid); } // 礼仪课老师信息 let lyteacher; if (classes.lyteacherid) { lyteacher = await this.heamodel.findById(classes.lyteacherid); if (!lyteacher) { lyteacher = await this.teamodel.findById(classes.lyteacherid); } } // 教课老师信息 let teachers = []; const lessones = await this.lessmodel.findOne({ classid }); if (lessones) { for (const lesson of lessones.lessons) { if (lesson.teaid) { const teacher = await this.teamodel.findById(lesson.teaid); teachers.push(teacher); } } } teachers.push(lyteacher); teachers.push(headteacher); teachers = _.uniq(_.compact(teachers)); for (const tea of teachers) { const user = users.find(item => item.uid === tea.id); if (user && user.openid) { const _tea = _.cloneDeep(JSON.parse(JSON.stringify(tea))); _tea.hasuserinfo = '1'; _.remove(teachers, tea); teachers.push(_tea); } } classes.teachers = teachers; return classes; } // 根据模板设置班级信息 async toSetClassSetting({ classid }) { const setting = await this.ctx.model.Setting.findOne(); if (!setting) throw new BusinessError(ErrorCode.DATA_NOT_EXIST, '未找到系统设置'); const { template_term } = setting; if (!template_term) throw new BusinessError(ErrorCode.DATA_NOT_EXIST, '未找到班级模板设置'); const templateList = await this.query({ termid: template_term }); const tClass = await this.model.findById(classid); if (!tClass) throw new BusinessError(ErrorCode.DATA_NOT_EXIST, '班级不存在,无法复制设定的班级设置'); const { name, termid, batchid } = tClass; const r = templateList.find(f => f.name === name); // 找到班主任全年计划 const trainPlan = await this.tmodel.findById(tClass.planid); if (!trainPlan) throw new BusinessError(ErrorCode.DATA_NOT_EXIST, '未找到该班所在的年度计划信息'); const tpt = trainPlan.termnum.id(tClass.termid); if (!tpt) throw new BusinessError(ErrorCode.DATA_NOT_EXIST, '未找到该班所在年度计划的期信息'); const tpb = tpt.batchnum.id(tClass.batchid); if (!tpb) throw new BusinessError(ErrorCode.DATA_NOT_EXIST, '未找到该班所在年度计划的批次信息'); if (!tpb.class) throw new BusinessError(ErrorCode.DATA_NOT_EXIST, '未找到该班所在年度计划批次下的班级'); const tpc = tpb.class.find(f => f.name === tClass.name); if (!tpc) throw new BusinessError(ErrorCode.DATA_NOT_EXIST, '未找到该班所在年度计划的班级信息'); const { headteacherid } = tpc; if (r) { // 说明这个是正常班,且从模板中找得到; 除了礼仪教师外,都复制过来 const { jslocationid, kbyslocationid, kzjhlocationid, yclocationid } = r; if (!tClass.jslocation && jslocationid) tClass.jslocationid = jslocationid; if (!tClass.kbyslocationid && kbyslocationid) tClass.kbyslocationid = kbyslocationid; if (!tClass.kzjhlocationid && kzjhlocationid) tClass.kzjhlocationid = kzjhlocationid; if (!tClass.yclocationid && yclocationid) tClass.yclocationid = yclocationid; if (!tClass.headteacherid && headteacherid) { tClass.headteacherid = headteacherid; // 默认班主任为礼仪教师 tClass.lyteacherid = headteacherid; } await tClass.save(); } else { // 没找到,有可能是普通班,也有可能是非普通班 // 找这个班级的同批次 const tClassBatch = await this.query({ termid, batchid }); const r = tClassBatch.find(f => ObjectId(tClass._id).equals(f._id)); const ri = tClassBatch.findIndex(f => ObjectId(tClass._id).equals(f._id)); // TODO 特殊班需要判断,如果没有就没有 // if (r) throw new BusinessError(ErrorCode.DATA_NOT_EXIST, '无法确定班级批次排序'); if (!r) { const { batch: tAllClassBatch } = r; const templateBatchList = templateList.filter(f => f.batch === tAllClassBatch); // 根据该班级所在批次的顺序,找到对应模板,然后复制 const copyTemplate = templateBatchList[ri]; const { jslocationid, kbyslocationid, kzjhlocationid, yclocationid } = copyTemplate; if (!tClass.jslocation && jslocationid) tClass.jslocationid = jslocationid; if (!tClass.kbyslocationid && kbyslocationid) tClass.kbyslocationid = kbyslocationid; if (!tClass.kzjhlocationid && kzjhlocationid) tClass.kzjhlocationid = kzjhlocationid; if (!tClass.yclocationid && yclocationid) tClass.yclocationid = yclocationid; if (!tClass.headteacherid && headteacherid) tClass.headteacherid = headteacherid; await tClass.save(); } } } } module.exports = ClassService;