apply.js 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417
  1. 'use strict';
  2. const assert = require('assert');
  3. const { after } = require('lodash');
  4. const _ = require('lodash');
  5. const moment = require('moment');
  6. const { ObjectId } = require('mongoose').Types;
  7. const { CrudService } = require('naf-framework-mongoose/lib/service');
  8. const { BusinessError, ErrorCode } = require('naf-core').Error;
  9. class ApplyService extends CrudService {
  10. constructor(ctx) {
  11. super(ctx, 'apply');
  12. this.model = this.ctx.model.Apply;
  13. this.tmodel = this.ctx.model.Teacher;
  14. this.submodel = this.ctx.model.Subject;
  15. this.trainmodel = this.ctx.model.Trainplan;
  16. this.umodel = this.ctx.model.User;
  17. this.nmodel = this.ctx.model.Notice;
  18. this.dayList = [ '日', '一', '二', '三', '四', '五', '六' ];
  19. }
  20. // 查询
  21. async queryteacher(query) {
  22. const { termid, subid, date } = query;
  23. const data = await this.model
  24. .find({ termid, subid, date })
  25. .sort({ msscore: -1 });
  26. const teachers = [];
  27. for (const _data of data) {
  28. const teacherid = _data.teacherid;
  29. const teacher = await this.tmodel.findById(teacherid);
  30. teachers.push(teacher);
  31. }
  32. return teachers;
  33. }
  34. // 教师计划初步课表安排,可反复使用
  35. async arrangeteacher({ planid }) {
  36. const trainplan = await this.trainmodel.findById(planid);
  37. if (!trainplan) {
  38. throw new BusinessError(ErrorCode.DATA_EXISTED, '年度计划不存在');
  39. }
  40. // trainplan = JSON.parse(JSON.stringify(trainplan));
  41. // 查找所有教师列表
  42. let teacherList = await this.tmodel.find({ xsscore: { $exists: true } });
  43. teacherList = JSON.parse(JSON.stringify(teacherList));
  44. // 查找所有教师上报列表
  45. let teaplanList = await this.model.find();
  46. teaplanList = JSON.parse(JSON.stringify(teaplanList));
  47. // 课程
  48. let subjectList = await this.submodel.find();
  49. subjectList = JSON.parse(JSON.stringify(subjectList));
  50. const termList = _.cloneDeep(trainplan);
  51. let { termnum } = termList;
  52. if (!termnum) return;
  53. termnum = JSON.parse(JSON.stringify(termnum));
  54. // 整理出课表
  55. const arr = this.setLessonList(termnum);
  56. // 安排后的课表
  57. const afterList = [];
  58. // 排课
  59. for (const l of arr) {
  60. const { termid, subid, day: date, teaid, status, batchid } = l;
  61. // 本期超过2次的教师列表,如果没有人就用这里分最高的排
  62. let outTwoTimesList = [];
  63. if (status && `${status}` === '1') {
  64. afterList.push(l);
  65. continue;
  66. }
  67. const subject = subjectList.find(f => ObjectId(subid).equals(f._id));
  68. if (subject.need_teacher !== '0') {
  69. afterList.push(l);
  70. continue;
  71. }
  72. // 申请该天,该科目的教师,并查出教师的名字,分数;并按分数排序
  73. let applyList = teaplanList.filter(
  74. f => f.date === date && f.subid === subid
  75. );
  76. applyList = applyList.map(i => {
  77. let obj = { ...JSON.parse(JSON.stringify(i)) };
  78. const r = teacherList.find(f => i.teacherid === f._id);
  79. if (r) {
  80. const { name: teaname, xsscore: score } = r;
  81. i.teaname = teaname;
  82. i.score = score * 1;
  83. obj = { ...obj, teaname, score };
  84. }
  85. return obj;
  86. });
  87. // 过滤出没有分数的,不排
  88. applyList = applyList.filter(f => f.score);
  89. // 按成绩排序
  90. applyList = _.orderBy(applyList, [ 'score' ], [ 'desc' ]);
  91. // 依次循环申请的教师列表,往这个课程安排中放教师
  92. for (const atea of applyList) {
  93. // 先查询,该教师,是否在今天有安排
  94. const tr = afterList.find(
  95. f => f.teaid === atea.teacherid && f.day === atea.date
  96. );
  97. if (tr) continue;
  98. // 查看这期内,每个申请上课的教师时候超过2天(2条记录),如果超过,则不排,但是如果最后没有人了,就得硬排了
  99. const r = afterList.filter(
  100. f => f.termid === termid && f.teaid === atea.teacherid
  101. );
  102. if (r.length >= 2) {
  103. outTwoTimesList = [ ...outTwoTimesList, atea ];
  104. continue;
  105. } else {
  106. l.teaid = atea.teacherid;
  107. l.teaname = atea.teaname;
  108. break;
  109. }
  110. }
  111. // 检查,该天,该科的课是否有教师
  112. const has_teaid = _.get(l, 'teaid');
  113. if (!has_teaid) {
  114. // // 如果没有教师,就需要在outTowTimesList列表中找分最高的教师
  115. const list = _.orderBy(outTwoTimesList, [ 'score' ], [ 'desc' ]);
  116. for (const i of list) {
  117. const tr = afterList.find(
  118. f => f.teaid === i.teacherid && f.day === i.date
  119. );
  120. if (tr) continue;
  121. else {
  122. l.teaid = i.teacherid;
  123. l.teaname = i.teaname;
  124. break;
  125. }
  126. }
  127. }
  128. afterList.push(l);
  129. }
  130. // 将afterList还原回正常的termnum;
  131. const newTermnum = this.returnTermnum(afterList, termnum);
  132. // 保存至计划
  133. trainplan.termnum = newTermnum;
  134. await trainplan.save();
  135. }
  136. // 确认计划安排
  137. async arrangeConfirm({ planid, ids }) {
  138. console.log(planid, ids);
  139. const trainplan = await this.trainmodel.findById(planid);
  140. if (!trainplan) {
  141. throw new BusinessError(ErrorCode.DATA_EXISTED, '年度计划不存在');
  142. }
  143. const plan = _.cloneDeep(trainplan);
  144. let { termnum } = plan;
  145. if (!termnum) return;
  146. termnum = JSON.parse(JSON.stringify(termnum));
  147. // 过滤出确认的期,TODO:没有做通知
  148. // termnum = termnum.filter(f => );
  149. // 找到每个教师的位置,然后把状态(status)改成1=>已确认
  150. for (const t of termnum) {
  151. if (!ids.includes(t._id)) continue;
  152. const { term } = t;
  153. if (!(t.batchnum && _.isArray(t.batchnum))) continue;
  154. for (const b of t.batchnum) {
  155. const { batch } = b;
  156. if (!(b.class && _.isArray(b.class))) continue;
  157. for (const c of b.class) {
  158. if (!(c.lessons && _.isArray(c.lessons))) continue;
  159. for (const l of c.lessons) {
  160. l.status = '1';
  161. }
  162. }
  163. }
  164. }
  165. trainplan.termnum = termnum;
  166. await trainplan.save();
  167. }
  168. /**
  169. * 拍平了的课表=>termnum
  170. * @param {Array} list 拍平了的课表,详情参考页面的初步课表的数据
  171. * @param {Array} termnum 原termnum
  172. */
  173. returnTermnum(list, termnum) {
  174. let newTermnum = [];
  175. for (const l of list) {
  176. const { termid, batchid, classid, ...info } = l;
  177. const updata = _.pick(info, [
  178. 'day',
  179. 'subid',
  180. 'subname',
  181. 'teaid',
  182. 'teaname',
  183. 'time',
  184. 'status',
  185. ]);
  186. newTermnum = termnum.map(t => {
  187. // 找到期
  188. if (termid === t._id) {
  189. t.batchnum = t.batchnum.map(b => {
  190. if (batchid === b._id) {
  191. // 找到批次
  192. b.class = b.class.map(c => {
  193. if (classid === c._id) {
  194. if (c.lessons) {
  195. // 说明有课程安排,找有没有重复的,没有就推进去,有就更改,subid查
  196. const r = c.lessons.find(f => f.subid === updata.subid);
  197. if (r) {
  198. const rindex = c.lessons.findIndex(
  199. f => f.subid === updata.subid
  200. );
  201. c.lessons[rindex] = updata;
  202. } else {
  203. c.lessons.push(updata);
  204. }
  205. } else {
  206. // 说明没有课程安排,放进去一条保存
  207. c.lessons = [ updata ];
  208. }
  209. }
  210. return c;
  211. });
  212. }
  213. return b;
  214. });
  215. }
  216. return t;
  217. });
  218. }
  219. return newTermnum;
  220. }
  221. /**
  222. * 将课表拍平了,从多维=>一维
  223. * @param {Array} termnum 计划的termnum
  224. */
  225. setLessonList(termnum) {
  226. let arr = [];
  227. for (const t of termnum) {
  228. const { batchnum, term, _id: termid } = t;
  229. // 班级和课程一一匹
  230. for (const b of batchnum) {
  231. const { class: classes, lessons, _id: batchid } = b;
  232. const claslesList = this.setList(
  233. term * 1,
  234. termid,
  235. batchid,
  236. classes,
  237. lessons
  238. );
  239. arr.push(...claslesList);
  240. }
  241. }
  242. arr = _.orderBy(arr, [ 'term', 'day' ], [ 'asc', 'asc' ]);
  243. return arr;
  244. }
  245. /**
  246. * 将课表模板和班级整理成一维数组
  247. * @param {String} term 期数
  248. * @param {String} termid 期id
  249. * @param {String} batchid 批id
  250. * @param {Array} classes 班级列表
  251. * @param {Array} lessonTemplate 课表模板
  252. */
  253. setList(term, termid, batchid, classes, lessonTemplate) {
  254. const arr = [];
  255. // 班级和课程匹配
  256. for (const cla of classes) {
  257. let { lessons } = cla;
  258. if (!lessons) lessons = lessonTemplate;
  259. for (const i of lessons) {
  260. let nobj = {};
  261. nobj.term = term;
  262. nobj.termid = termid;
  263. nobj.batchid = batchid;
  264. nobj.type = cla.type;
  265. const obj = _.omit(cla, [ 'lessons' ]);
  266. nobj.classid = _.clone(cla._id);
  267. nobj = _.assign(nobj, obj);
  268. nobj = _.assign(nobj, i);
  269. arr.push(nobj);
  270. }
  271. }
  272. return arr;
  273. }
  274. /**
  275. * 发送消息
  276. * @param {Object} param planid:年度计划id,ids,发送的期列表;classtype:发送班级类型 undefined 都发,有的话就找指定班级类型发
  277. */
  278. async arrangeSendMsg({ planid, ids, classtype }) {
  279. const trainplan = await this.trainmodel.findById(planid);
  280. if (!trainplan) {
  281. throw new BusinessError(ErrorCode.DATA_EXISTED, '年度计划不存在');
  282. }
  283. // 大批次id,年度计划id
  284. const plan = _.cloneDeep(trainplan);
  285. let { termnum, planyearid } = plan;
  286. if (!termnum) return;
  287. termnum = JSON.parse(JSON.stringify(termnum));
  288. // 整理出课表
  289. let arr = this.setLessonList(termnum);
  290. // 过滤出需要发送的教师
  291. arr = arr.filter(f => ids.find(id => f.termid === id) && f.teaid);
  292. // && f.status !== '1'
  293. // 整理出要发送的教师列表
  294. let teaids = arr.map(i => i.teaid);
  295. teaids = _.uniq(teaids);
  296. // 找到教师信息
  297. let teaList = await this.tmodel.find({ _id: teaids });
  298. // 找到教师用户信息
  299. let teauserList = await this.umodel.find({ uid: teaids });
  300. if (teaList) teaList = JSON.parse(JSON.stringify(teaList));
  301. if (teauserList)teauserList = JSON.parse(JSON.stringify(teauserList));
  302. // 发送,此处是根据安排,给教师发.还有一种方案是根据教师,整理安排一起发送
  303. // 查询是否发送过这期的通知
  304. // 排序
  305. arr = _.orderBy(arr, [ 'day' ], [ 'asc' ]);
  306. for (const l of arr) {
  307. // 教师id,期数,班级名,上课的日期,课程名
  308. const { teaid, term, name, day, subname, termid, classid, type, status } = l;
  309. // 已确认的教师不发信息
  310. if (status === '1') continue;
  311. // 判断发送的班级类型
  312. if (!(classtype && classtype === type)) continue;
  313. const tea = teaList.find(f => f._id === teaid);
  314. const teauser = teauserList.find(f => f.uid === teaid);
  315. // 文案
  316. let msg = `${_.get(tea, 'name', '')}老师您好:
  317. 吉林省高等学校毕业生就业指导中心-双困生培训系统提醒您:
  318. ${term}期-${name.includes('班') ? name : `${name}班`}
  319. ${day}(星期${this.dayList[moment(day).days()]})
  320. 有您的课程安排:${subname}`;
  321. msg = `${msg}\n 如果您无法进行授课,请及时联系中心负责人`;
  322. const { openid } = teauser;
  323. let tourl;
  324. let to_send = false;
  325. if (openid) {
  326. let notice = await this.nmodel.findOne({ planid, termid, classid, type: '6' });
  327. // 找下是否发过信息
  328. if (notice) {
  329. // 发过信息,找有没有这个教师
  330. const { notified } = notice;
  331. if (_.isArray(notified)) {
  332. const has_notice = notified.find(f => f.notifiedid === teaid);
  333. if (has_notice) {
  334. const { status } = has_notice;
  335. if (status !== '1') to_send = true;
  336. } else {
  337. const obj = { notifiedid: teaid, username: _.get(tea, 'name', ''), content: msg };
  338. notice.notified.push(obj);
  339. await notice.save();
  340. to_send = true;
  341. }
  342. }
  343. } else {
  344. const notified = [{ notifiedid: teaid, username: _.get(tea, 'name', ''), content: msg }];
  345. const noticeObj = { planyearid, planid, termid, classid, noticeid: 'system', type: '6', content: `${term}期-${name.includes('班') ? name : `${name}班`}教师计划初步信息确认`, notified };
  346. await this.nmodel.create(noticeObj);
  347. notice = await this.nmodel.findOne({ planid, termid, classid, type: '6' });
  348. to_send = true;
  349. }
  350. tourl = this.ctx.app.config.baseUrl + '/msgconfirm/?userid=' + teaid + '&noticeid=' + notice._id;
  351. }
  352. if (to_send) {
  353. // 邮箱与微信都发送
  354. const { email } = tea;
  355. if (email) {
  356. this.toSendEmail(email, msg, tea.name);
  357. }
  358. if (openid) {
  359. this.toSendWxMsg(openid, msg, tea.name, tourl);
  360. }
  361. }
  362. }
  363. }
  364. /**
  365. * 计划-教师初步课表发送邮件
  366. * @param {String} email 邮件
  367. * @param {String} content 内容
  368. * @param {String} teaname 教师姓名
  369. */
  370. async toSendEmail(email, content, teaname) {
  371. if (!email) {
  372. console.error(`计划教师发送通知:${teaname}没有email`);
  373. return;
  374. }
  375. const subject = '吉林省高等学校毕业生就业指导中心通知(系统邮件,请勿回复)'; //
  376. this.ctx.service.util.sendMail(email, subject, content);
  377. }
  378. /**
  379. * 计划-教师初步课表发送微信推送
  380. * @param {String} openid 微信公众号的openid
  381. * @param {String} content 内容
  382. * @param {String} teaname 教师姓名
  383. * @param {String} tourl 确认地址
  384. */
  385. async toSendWxMsg(openid, content, teaname, tourl) {
  386. if (!openid) {
  387. console.error(`计划教师发送微信推送:${teaname}没有openid`);
  388. return;
  389. }
  390. // TODO or notTODO 发送微信推送记录
  391. await this.ctx.service.weixin.sendTemplateDesign(
  392. this.ctx.app.config.REVIEW_TEMPLATE_ID,
  393. openid,
  394. '您有一个新的通知',
  395. '您有新的安排',
  396. content,
  397. '感谢您的使用',
  398. tourl
  399. );
  400. }
  401. }
  402. module.exports = ApplyService;