123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576 |
- '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;
|