'use strict'; const assert = require('assert'); const { groupEnd } = require('console'); const _ = require('lodash'); const { ObjectId } = require('mongoose').Types; const moment = require('moment'); const { CrudService } = require('naf-framework-mongoose/lib/service'); const { BusinessError, ErrorCode } = require('naf-core').Error; class LessonService extends CrudService { constructor(ctx) { super(ctx, 'lesson'); this.model = this.ctx.model.Lesson; this.tmodel = this.ctx.model.Trainplan; this.clamodel = this.ctx.model.Class; this.lmodel = this.ctx.model.Lessonmode; this.teamodel = this.ctx.model.Teacher; this.stumodel = this.ctx.model.Student; this.schmodel = this.ctx.model.School; this.headteamodel = this.ctx.model.Headteacher; this.umodel = this.ctx.model.User; this.nmodel = this.ctx.model.Notice; this.weekList = [ '日', '一', '二', '三', '四', '五', '六' ]; } // 自动排课私有方法 async autolesson({ id }) { // 首先将课程表清空 const res = await this.tmodel.findById(id); if (!res) { throw new BusinessError(ErrorCode.DATA_NOT_EXIST, '全年计划信息不存在'); } const terms = res.termnum; const _lessonmode = await this.lmodel.find(); // 循环取得所有期 for (const elm of terms) { // 根据期id清空课程表 await this.model.deleteMany({ termid: elm.id }); // 清空成功后,循环取得当前期下所有批次信息 const batchs = elm.batchnum; for (const batch of batchs) { // 取得当前批次开始结束日期,并根据日期取得所有天数 const sedays = await this.getAllDays(batch.startdate, batch.enddate); // 根据批次取得当前批次下所有班级 const _classs = await this.clamodel.find({ planid: id, termid: elm.id, batchid: batch.id, }); // 循环班级 const teachids = []; for (const cla of _classs) { // 取得课程模板信息 let lessonmode_ = _.find(_lessonmode, { type: cla.type }); if (!lessonmode_) { lessonmode_ = _lessonmode[0]; if (!lessonmode_) { throw new BusinessError( ErrorCode.DATA_NOT_EXIST, '课程模板信息不存在' ); } } // 取得模板内容并转化成json const lessons_ = JSON.parse(lessonmode_.lessons); // 记录天数 let i = 1; // 循环天数 const newlesson = []; for (const day of sedays) { // 循环课程模板,将模板信息排入班级课程表中 for (const lessm of lessons_) { // 循环插入模板信息 if (lessm['day' + i] !== '--') { let _subid = ''; if (lessm['day' + i + 'type'] === '课程') { _subid = lessm['day' + i + 'subid']; } else { _subid = ''; } let allday = 0; if (i === 6) { // 判断是否有外市的学生有的时候 将其设置为半天 const ishalfday = await this.ishalfday(cla.id); if (ishalfday) { allday = 1; } } const data = { subid: _subid, subname: lessm['day' + i], date: day, time: lessm.time, day: allday, }; // 将教师按照分数的综合成绩排序,上报时间,安排教师. const teacher_ = await this.autoteacher(_subid, teachids); if (teacher_) { data.teaid = teacher_.id; data.teaname = teacher_.name; teachids.push(teacher_.id); } newlesson.push(data); } } i = i + 1; } const newdata = { termid: elm.id, batchid: batch.id, classid: cla.id, lessons: newlesson, }; // 将课程信息填入课程表 await this.model.create(newdata); } } } } // 自动排教师,按照分数的综合成绩排序,上报时间,安排教师 async autoteacher(subid, teachids) { // 按照上报时间取得所有老师,进行正序排列 const teachers = await this.teamodel .find({ subid, status: '4' }) .sort({ zlscore: '-1', msscore: '-1', xsscore: '-1' }); for (const teaid of teachids) { _.remove(teachers, item => item.id === teaid); } let teacher = {}; if (teachers.length > 0) { teacher = teachers[0]; } return teacher; } // 判断是否为半天 async ishalfday(classid) { // 通过班级id取得所有学生 const students = await this.stumodel.find({ classid }); let res = false; for (const stu of students) { const sch = await this.schmodel.findOne({ code: stu.schid }); if (sch && sch.hascar === '0') { res = true; break; } } return res; } // 取得日期间所有日期 async getAllDays(begin_date, end_date) { const errArr = [], resultArr = [], dateReg = /^[2]\d{3}-[01]\d-[0123]\d$/; if ( typeof begin_date !== 'string' || begin_date === '' || !dateReg.test(begin_date) ) { return errArr; } if ( typeof end_date !== 'string' || end_date === '' || !dateReg.test(end_date) ) { return errArr; } try { const beginTimestamp = Date.parse(new Date(begin_date)), endTimestamp = Date.parse(new Date(end_date)); // 开始日期小于结束日期 if (beginTimestamp > endTimestamp) { return errArr; } // 开始日期等于结束日期 if (beginTimestamp === endTimestamp) { resultArr.push(begin_date); return resultArr; } let tempTimestamp = beginTimestamp, tempDate = begin_date; // 新增日期是否和结束日期相等, 相等跳出循环 while (tempTimestamp !== endTimestamp) { resultArr.push(tempDate); // 增加一天 tempDate = moment(tempTimestamp).add(1, 'd').format('YYYY-MM-DD'); // 将增加时间变为时间戳 tempTimestamp = Date.parse(new Date(tempDate)); } // 将最后一天放入数组 resultArr.push(end_date); return resultArr; } catch (err) { return errArr; } } // 根据计划id、教师id查询所有班级信息 async classbyteaid({ planid, teaid }) { // 取得传入的计划id与教师id // 根据计划id取得所有期 const plan = await this.tmodel.findById(planid); if (!plan) { throw new BusinessError(ErrorCode.DATA_NOT_EXIST, '全年计划信息不存在'); } const terms = await plan.termnum; // 循环取得所有期信息 const data = []; for (const term of terms) { // 根据期id与教师id查出课程班级信息 const lessons = await this.model.find({ termid: term.id, 'lessons.teaid': teaid, }); const batchs = await term.batchnum; for (const elm of lessons) { const newdata = {}; const batch = await batchs.id(elm.batchid); newdata.planid = planid; newdata.title = plan.title; newdata.termid = elm.termid; newdata.term = term.term; newdata.batchid = elm.batchid; newdata.batch = batch.batch; newdata.classid = elm.classid; if (elm.classid) { const cla = await this.clamodel.findById(elm.classid); if (cla) { newdata.classname = cla.name; } } const _lessons = elm.lessons.filter(item => item.teaid === teaid); newdata.lessons = _lessons; data.push(newdata); } } return data; } // 根据计划id、教师id查询所有班级信息 async teaclass({ termid, teaid }) { // 取得传入的计划id与教师id // 根据计划id取得所有期 const lessons = await this.model.find({ termid, 'lessons.teaid': teaid, }); const classids = lessons.map(i => ObjectId(i.classid)); let data = await this.ctx.model.Class.find({ _id: { $in: classids } }).exec(); if (data.length <= 0) return; data = JSON.parse(JSON.stringify(data)); const planid = data[0].planid; const trainPlan = await this.tmodel.findById(planid).lean(); const { termnum = [] } = trainPlan; if (termnum.length <= 0) return; const termData = termnum.find(f => ObjectId(f._id).equals(termid)); if (!termData) return; const { batchnum = [], term } = termData; if (batchnum.length <= 0) return; data = data.map(i => { i.term = term; const batchData = batchnum.find(f => ObjectId(f._id).equals(i.batchid)); if (batchData) { i.batch = batchData.batch; i.startdate = batchData.startdate; i.enddate = batchData.enddate; } return i; }); return data; } async uplessones(data) { for (const _data of data) { await this.model.findByIdAndUpdate(_data.id, _data); } } /** * 修改课表的状态,并发送信息 * @param {Array} ids 要修改的课表 * @property {Array} classids 班级表ids */ async check({ ids, classids }) { // 1,修改课表状态; TODO 2,拿到所有的班级,获取所有人员;3,然后发送信息 const list = await this.model.find({ _id: { $in: ids }, classid: classids }); const res = await this.model.updateMany({ _id: { $in: ids }, classid: classids }, { status: '1' }); const noticeList = []; const defaults = { noticeid: 'system', type: '4', }; // 循环课表 for (const l of list) { const planRes = await this.tmodel.findOne({ termnum: { $elemMatch: { _id: l.termid } } }); const { planyearid, _id: planid } = planRes; if (!planRes) continue; // 教师 需要知道 期,批,班 日期 星期 科目 上课地点 班主任 班主任电话 // 先找到这个课表是哪个班的 const { classid } = l; if (!classid) { console.error(`不存在班级id为=>${classid}`); continue; } const classInfo = await this.ctx.service.class.fetch({ id: classid }); if (!classInfo) { console.error(`没有id为=>${classid} 的班级信息`); continue; } const { termid } = classInfo; // 班主任信息 let data = await this.getHeadTeacherMsg(classInfo); // 礼仪教师信息 let lydata = await this.getLyTeacherMsg(classInfo); // 整理信息 data = { ...data, ...defaults, termid, classid, planyearid, planid }; if (lydata) { lydata = { ...lydata, ...defaults, termid, classid, planyearid, planid }; noticeList.push(lydata); } // const dirIsBefore = moment(moment().format('YYYY-MM-DD')).isSameOrBefore(classInfo.startdate); // isSameOrBefore // if (dirIsBefore) { // 班主任,礼仪教师,不限制时间问题,只要未确认,就发送 noticeList.push(data); // } const { lessons } = l; let have_teacherLesson = lessons.filter(f => f.teaid); have_teacherLesson = JSON.parse(JSON.stringify(have_teacherLesson)); const newArr = []; for (const l of have_teacherLesson) { // 整理时间 TODO,不需要合并时间了,先留着,之后真不需要就删掉 const { subid, teaid, date } = l; // 超过日期了就不发了 const isBefore = moment(moment().format('YYYY-MM-DD')).isSameOrBefore(date); if (!isBefore) continue; if (!subid && teaid) continue; const r = newArr.find(f => f.subid === subid && f.teaid === teaid); const ri = newArr.findIndex( f => f.subid === subid && f.teaid === teaid ); // 如果找到了,就要把这个时间和上一个整合 if (r) { newArr[ri].timeList.push(l.time); } else { let obj = _.cloneDeep(l); // 没找到,就放进去 const timeList = [ l.time ]; obj = { ...obj, timeList }; newArr.push(obj); } } for (const l of newArr) { let teamsg = await this.getTeacherMsg(classInfo, l); teamsg = { ...teamsg, ...defaults, termid, classid, planyearid, planid }; noticeList.push(teamsg); } } await this.toSendMsg(noticeList); } // 给班主任发信息 async getHeadTeacherMsg(classInfo) { // 班主任 需要知道 期,批,班,时间段,星期段,礼仪课教师,用餐地点,拓展训练地点,开班仪式地点,上课地点 const { term, batch, headteacher, name, headteacherid, startdate, enddate } = classInfo; if (!headteacherid) return; let msg = `班主任-${headteacher},中心已经安排您为: ${term}期-${name.includes('班') ? name : `${name}班`} 班主任`; if (startdate && enddate) msg = `${msg} \n 时间为:${startdate}(星期${this.weekList[moment(startdate).day()]}) 至 ${enddate} (星期${this.weekList[moment(enddate).day()]})`; if (_.get(classInfo, 'kbyslocationid'))msg = `${msg} \n 开班地点为:${_.get(classInfo, 'kbyslocation')}`; if (_.get(classInfo, 'kzjhlocationid'))msg = `${msg} \n 拓展训练地点为:${_.get(classInfo, 'kzjhlocation')}`; if (_.get(classInfo, 'jslocationid'))msg = `${msg} \n 上课地点为:${_.get(classInfo, 'jslocation')}`; if (_.get(classInfo, 'yclocationid'))msg = `${msg} \n 用餐地点为:${_.get(classInfo, 'yclocation')}`; // 礼仪教师需要查询,然后带上电话 if (_.get(classInfo, 'lyteacherid')) { msg = `${msg} \n 礼仪课教师为: \n ${_.get(classInfo, 'lyteacher')}`; const { lyteacherid } = classInfo; const r = await this.umodel.findOne({ uid: lyteacherid }); if (r) { const { mobile } = r; if (mobile) msg = `${msg} 电话:${mobile}`; } } // 查出openid,email const info = await this.getSendInfo(headteacherid); const obj = { notifiedid: headteacherid, username: headteacher, content: msg, ncontent: `${term}期-${batch}批-${name.includes('班') ? name : `${name}班`}课表确认` }; if (info) { const { openid, email } = info; obj.openid = openid; obj.email = email; } return obj; } // 给教师发送信息 async getTeacherMsg(classInfo, lessonInfo) { if (!classInfo || !lessonInfo) return; const { term, batch, name, headteacherid } = classInfo; const { date, subname, timeList } = lessonInfo; let msg = `教师-${lessonInfo.teaname}您好,中心已经为您安排了 \n ${term}期-${name.includes('班') ? name : `${name}班`}`; if (date) msg = `${msg} \n 上课日期:${date}(星期${this.weekList[moment(date).day()]})`; // if (timeList && _.isArray(timeList)) { // msg = `${msg} \n 上课时间:`; // for (const time of timeList) { // msg = `${msg} ${time}`; // } // } if (subname) msg = `${msg} \n 课程:${subname}`; if (_.get(classInfo, 'jslocationid'))msg = `${msg} \n 上课教室地点为:${_.get(classInfo, 'jslocation')}`; if (_.get(classInfo, 'headteacherid')) { msg = `${msg} \n 班主任为: \n ${_.get(classInfo, 'headteacher')}`; if (headteacherid) { const r = await this.umodel.findOne({ uid: headteacherid }); if (r) { const { mobile } = r; if (mobile) msg = `${msg} 电话:${mobile}`; } } } const info = await this.getSendInfo(lessonInfo.teaid); const obj = { notifiedid: _.get(lessonInfo, 'teaid'), username: _.get(lessonInfo, 'teaname'), content: msg, ncontent: `${term}期-${batch}批-${name.includes('班') ? name : `${name}班`}课表确认` }; if (info) { const { openid, email } = info; obj.openid = openid; obj.email = email; } return obj; } // 给礼仪教师发送信息 async getLyTeacherMsg(classInfo) { const { term, batch, name, headteacherid, lyteacherid, lyteacher } = classInfo; if (headteacherid === lyteacherid) return; let msg = `${lyteacher}您好,中心为您安排了\n ${term}期-${name.includes('班') ? name : `${name}班`}的礼仪课`; if (_.get(classInfo, 'jslocationid'))msg = `${msg} \n 上课教室地点为:${_.get(classInfo, 'jslocation')}`; if (_.get(classInfo, 'headteacherid')) { msg = `${msg} \n 班主任为: \n ${_.get(classInfo, 'headteacher')}`; if (headteacherid) { const r = await this.umodel.findOne({ uid: headteacherid }); if (r) { const { mobile } = r; if (mobile) msg = `${msg} 电话:${mobile}`; } } } const info = await this.getSendInfo(lyteacherid); const obj = { notifiedid: lyteacherid, username: lyteacher, content: msg, ncontent: `${term}期-${batch}批-${name.includes('班') ? name : `${name}班`}课表确认` }; if (info) { const { openid, email } = info; obj.openid = openid; obj.email = email; } return obj; } // 查找openid和emaiil async getSendInfo(uid) { const user = await this.umodel.findOne({ uid }); if (!user) return; const { type, openid } = user; let email; // type =1班主任,type = 3 教师 if (type === '1') { const info = await this.headteamodel.findOne({ _id: ObjectId(uid) }); if (info) { const { qq } = info; if (qq) email = `${qq}@qq.com`; } } else if (type === '3') { const info = await this.teamodel.findOne({ _id: ObjectId(uid) }); if (info) email = info.email; } return { openid, email }; } async toSendMsg(noticeList) { for (const notice of noticeList) { // 先找信息notice,然后查看有没有这个人的信息,有就发送,没有就添加,再发送 // 课表通知,1个班的信息放一起,按班级来看 const { planyearid, planid, termid, classid, noticeid, type, ncontent, content, username, notifiedid, email, openid } = notice; // await this.nmodel.deleteMany({ termid, planid, classid, type }); let nres = await this.nmodel.findOne({ termid, planid, classid, type }); if (openid) { // 排除重复,没有的填进对应的班级中 if (!nres) { // 组织数据,存起来 const notified = [{ content, username, notifiedid }]; const nobj = { planyearid, planid, termid, classid, noticeid, type, content: ncontent, notified }; await this.nmodel.create(nobj); nres = await this.nmodel.findOne({ termid, planid, classid, type }); } else { // 有该班的通知,然后查看是否有这个人,没有,加进去,有这个人,不管 if (nres.notified && _.isArray(nres.notified)) { const r = nres.notified.find(f => f.notifiedid === notifiedid); // 没有人,加进去,保存 if (!r) { nres.notified.push({ content, username, notifiedid }); await nres.save(); } else { // 有这个人,需要查这个人是否接收到信息,接收到信息,就不需要再次发送了 const { status } = r; if (status !== '0') continue; } } } } // 邮件 if (email) { const subject = '吉林省高等学校毕业生就业指导中心通知'; this.ctx.service.util.sendMail(email, subject, content); } if (openid) { const tourl = this.ctx.app.config.baseUrl + '/msgconfirm/?userid=' + notifiedid + '¬iceid=' + nres._id; // TODO 推送 this.ctx.service.weixin.sendTemplateDesign( this.ctx.app.config.REVIEW_TEMPLATE_ID, openid, '您有一个新的通知,请点击信息,确认您已收到信息!', '您的安排', content, '感谢您的使用', tourl ); } } } // 新排课,从计划中拿出来对应的课表 async newArrange({ planid, termid }) { const lmodelList = await this.lmodel.find(); const trainplan = await this.tmodel.findById(planid); if (!trainplan) throw new BusinessError(ErrorCode.DATA_NOT_EXIST, '计划不存在'); let { termnum } = trainplan; // 指定期的数据 termnum = JSON.parse(JSON.stringify(termnum)); if (!termnum) throw new BusinessError(ErrorCode.DATA_NOT_EXIST, '该期不存在'); // 该期的课表,没有就添加 const has_lesson = await this.model.find({ termid }); const classids = has_lesson.map(i => ObjectId(i.classid)); const has_classList = await this.clamodel.find({ _id: { $in: classids } }, { name: 1 }); const sterm = termnum.find(f => f._id === termid); const { batchnum } = sterm; // 确保batchnum是数组 if (!_.isArray(batchnum)) throw new BusinessError(ErrorCode.DATA_NOT_EXIST, '该期下批次数据错误'); for (const b of batchnum) { const { _id: batchid, class: classArrange, startdate, enddate } = b; const classList = await this.clamodel.find({ planid, termid, batchid }); // 确保list是数组 if (!classList || classList.length === 0) continue; const clist = JSON.parse(JSON.stringify(classList)); for (const c of clist) { // 排指定班 const { name, type, ...lessinfo } = await this.partsOfClass(c); // 查看该班级是否已经有课表 const is_has = has_classList.find(f => f.name === name); if (is_has) continue; // 找到指定班级的安排 这里目前只能用name找,这个地方很不稳定,需要找到其他的具有唯一性的判断来执行这个find const clalr = classArrange.find(f => f.name === name); // 没找到指定班级的安排 if (!clalr) { console.error('没有找到指定安排'); continue; } const { lessons: ntemplate } = clalr; // 如果没有模板 if (!ntemplate) { console.error('没有找到指定班级安排模板'); continue; } // 根据班级类型找到原课表模板 let lessonModel = lmodelList.find(f => f.type === type); lessonModel = JSON.parse(JSON.stringify(lessonModel)); // 原模板 let { lessons: otemplate } = lessonModel; otemplate = JSON.parse(otemplate); // 日期列表,感谢裕哥能给我留个可用的东西 const dayList = await this.getAllDays(startdate, enddate); const lessons = []; for (let i = 0; i < dayList.length; i++) { for (const ot of otemplate) { const date = dayList[i]; const { time } = ot; const keys = Object.keys(ot).filter(f => f.includes(`day${i + 1}`)); const kvs = {}; for (const key of keys) { // 将原课表的每日,每个时间段的安排整理成object if (key.startsWith('day') && key.endsWith('type')) { // kvs.type = ot[key]; } else if (key.startsWith('day') && key.endsWith('subid')) { kvs.subid = ot[key]; } else { kvs.subname = ot[key]; } } // 整理完的object,如果有subid,就是课程,找教师 const tsubid = _.get(kvs, 'subid'); if (tsubid) { const r = ntemplate.find(f => f.subid === tsubid); if (r) { const { teaid, teaname } = r; if (teaid && teaname) { kvs.teaid = teaid; kvs.teaname = teaname; } } } const obj = { date, time, ...kvs, day: '0' }; lessons.push(obj); } } // 最后整合 const classLesson = { ...lessinfo, lessons }; // 保存 await this.model.create(classLesson); } } } // 新排课,将班级层面处理抽出来 async partsOfClass(classinfo) { classinfo = JSON.parse(JSON.stringify(classinfo)); const { termid, batchid, _id: classid, name, type } = classinfo; const obj = { termid, batchid, classid, name, type }; return obj; } async teaIndex({ planid, teaid }) { const trainplan = await this.tmodel.findById(planid); if (!trainplan) throw new BusinessError(ErrorCode.DATA_NOT_EXIST, '未找到指定年度计划'); const { termnum } = trainplan; const termids = termnum.map(i => i._id); let lessons = await this.model.find({ termid: termids, status: '1', lessons: { $elemMatch: { teaid } } }); if (lessons.length > 0)lessons = JSON.parse(JSON.stringify(lessons)); else return []; const classids = lessons.map(i => i.classid); // 找期,班级信息 const classes = await this.clamodel.find({ _id: classids }); let res = []; for (const i of lessons) { const { lessons: ls, classid, termid } = i; let term, cla; const list = ls.filter(f => f.teaid === teaid); const t = termnum.find(f => ObjectId(f._id).equals(termid)); if (t) term = t.term; const c = classes.find(f => ObjectId(f._id).equals(classid)); if (c) cla = c.name; for (const l of list) { const { subname, date } = l; if (cla && subname && date && term) { res.push({ subname, date, term, class: cla }); } } } res = _.uniqWith(res, _.isEqual); return res; } // 按系统设置->课表模板,进行对应班级类型的时间更新 async timeCollate({ termid }) { // 找到课表模板 const templates = await this.lmodel.find(); // 找到该期下的班级 const classes = await this.clamodel.find({ termid }); // 找到这些班级的课表 const cids = classes.map(i => i._id); let lList = await this.model.find({ classid: cids }); if (lList.length > 0) lList = JSON.parse(JSON.stringify(lList)); for (const l of lList) { // 确定是班级类型 const c = classes.find(f => ObjectId(f._id).equals(l.classid)); if (!c) continue; // 确定模板拿哪个类型 const { type } = c; const t = templates.find(f => f.type === type); if (!t) continue; // 整理出时间列表(默认顺序就可以) const { lessons: tl } = t; const tlarr = JSON.parse(tl); const timeArr = tlarr.map(i => i.time); // 改课表中的每天的时间 // 1,需要按天分组,然后进行排序 const { lessons: lls } = l; const glls = _.groupBy(lls, 'date'); const llsVal = Object.values(glls); // console.log(llsVal); let lastData = []; // 副本,排序,更新时间,赋回 for (const ll of llsVal) { let dup = _.cloneDeep(ll); dup = this.sortLesson(dup); for (let i = 0; i < dup.length; i++) { dup[i].time = timeArr[i]; } lastData.push(dup); } lastData = _.flatten(lastData); l.lessons = lastData; await this.model.updateOne({ _id: l._id }, { lessons: lastData }); } } // 按时间升序排序 sortLesson(arr) { arr = arr.map(i => { i.fulltime = `${i.date} ${i.time}`; return i; }); arr = _.orderBy(arr, [ 'fulltime' ], [ 'asc' ]); arr = arr.map(i => _.omit(i, [ 'fulltime' ])); return arr; } } module.exports = LessonService;