class.js 28 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730
  1. 'use strict';
  2. const assert = require('assert');
  3. const _ = require('lodash');
  4. const { ObjectId } = require('mongoose').Types;
  5. const { CrudService } = require('naf-framework-mongoose/lib/service');
  6. const { BusinessError, ErrorCode } = require('naf-core').Error;
  7. class ClassService extends CrudService {
  8. constructor(ctx) {
  9. super(ctx, 'class');
  10. this.model = this.ctx.model.Class;
  11. this.stumodel = this.ctx.model.Student;
  12. this.lessmodel = this.ctx.model.Lesson;
  13. this.umodel = this.ctx.model.User;
  14. this.tmodel = this.ctx.model.Trainplan;
  15. this.gmodel = this.ctx.model.Group;
  16. this.heamodel = this.ctx.model.Headteacher;
  17. this.teamodel = this.ctx.model.Teacher;
  18. this.locamodel = this.ctx.model.Location;
  19. }
  20. async divide(data) {
  21. // 21-04-27重做
  22. const { planid, termid } = data;
  23. assert(planid, '计划id为必填项');
  24. assert(termid, '期id为必填项');
  25. // 先自动生成班级 TODO:之后放开
  26. await this.autoclass(planid, termid);
  27. // 根据计划id与期id查询所有批次下的班级
  28. const newclass = await this.model.find({ planid, termid });
  29. if (!newclass) {
  30. throw new BusinessError(ErrorCode.DATA_NOT_EXIST, '班级信息不存在');
  31. }
  32. // 根据计划和期
  33. // 查询所有上报的学生 并按照学校排序
  34. const newstudent = await this.stumodel.find({ termid }).sort({ schid: 1 });
  35. if (!newstudent) {
  36. throw new BusinessError(ErrorCode.DATA_NOT_EXIST, '学生信息不存在');
  37. }
  38. // 按批次分组,每个批次处理自己的
  39. const claGroup = _.groupBy(newclass, 'batchid');
  40. const keys = Object.keys(claGroup);
  41. const result = {}; // key:班级id;value:学生id数组
  42. let cantSolveNotice = false;
  43. // 循环批次
  44. for (const bkey of keys) {
  45. const classList = claGroup[bkey]; // 该批次下的班级列表
  46. const batchStudentList = _.shuffle(newstudent.filter(f => f.batchid === bkey)); // shuffle:打乱顺序 该批次下的学生列表
  47. // 分为多个学校 男/女 数组
  48. // 按学校分组
  49. const sgroup = _.groupBy(batchStudentList, 'schid');
  50. // 学校代码key
  51. const schKeys = Object.keys(sgroup);
  52. let solve = {};
  53. const studentGroup = {}; // key:学校id-boy/girl;value:对应学校,性别的学生列表
  54. const classnum = classList.length; // 班级数
  55. const minClass = _.minBy(classList, i => parseInt(i.number)); // 该批次人数最少的班级
  56. let minNumber = 0;
  57. if (minClass) minNumber = parseInt(minClass.number);
  58. for (const skey of schKeys) {
  59. const schstus = sgroup[skey];
  60. // 获得每个学校的男/女人数
  61. const boys = schstus.filter(f => f.gender.includes('男'));
  62. const girls = schstus.filter(f => f.gender.includes('女'));
  63. studentGroup[`${skey}-boy`] = boys;
  64. studentGroup[`${skey}-girl`] = girls;
  65. // 算出平均分配解:每个 学校 固定向 每个班 派多少 男/女生
  66. // 需要验证最少的班级人数:因为平均后,可能造成人多了,要吐出来的
  67. const br = this.numDivide(boys.length, classnum);
  68. const gr = this.numDivide(girls.length, classnum);
  69. // 存入最佳计算结果,但是需要验证,看看这批次的计算结果是否<=班级最少人数;多了可是要吐人的.吐人的写法代码更多
  70. solve[`${skey}-boy`] = br;
  71. solve[`${skey}-girl`] = gr;
  72. }
  73. // 验证最佳结果是否超出班级的最少人数,不是,则处理
  74. // console.log('计算最优解');
  75. solve = this.checkSolve(solve, minNumber, classnum);
  76. // 塞人
  77. // console.log('塞人前');
  78. for (const c of classList) {
  79. const { _id } = c;
  80. if (!(result[_id] && _.isArray(result[_id]))) result[_id] = [];
  81. for (const key in solve) {
  82. const { res } = solve[key];
  83. let sList = studentGroup[key];
  84. // 取出指定人数
  85. const inputs = _.take(sList, res);
  86. // 放进结果里
  87. // .map(i => i._id)
  88. result[_id].push(...inputs);
  89. // 删除指定人数
  90. sList = _.drop(sList, res);
  91. // 赋值回去
  92. studentGroup[key] = sList;
  93. }
  94. }
  95. // console.log('塞人后');
  96. // console.log('补人前');
  97. // 检查是否有学生剩余,如果有学生剩余,都需要继续进行人的处理,无论是补人还是加人.
  98. let els = _.flatten(Object.values(studentGroup)).length;
  99. // 无法解决,需要手动分配标识
  100. let cantSolve = false;
  101. // 没有剩余的学生,下面也没法处理,结束了(跳过while了)
  102. while (els > 0 && !cantSolve) {
  103. // 还有学生,那就需要继续处理,看看是补人还是加人
  104. // 检查各个班级是否达到人数要求
  105. const { ok, not } = this.checkClassStatus(result, classList);
  106. // 如果not里有值,说明还有班级没满足人数要求->补人
  107. // 如果not没有值,说明参培人员多了->分到每个班里
  108. if (not.length > 0) {
  109. // 进行补人
  110. // not中的每个班进行补人,每个班补一个人(result[classid]加人),studentGroup[key]减人
  111. // 如果没人了,break;
  112. for (const c of not) {
  113. // 需要计算出这个班补人的最优解
  114. const { _id } = c;
  115. const stuList = result[_id];
  116. const claSolve = this.getClassSolve(stuList);
  117. // 没有最优解=>没有班级有学生.人数甚至不满足把班级填满.需要手动安排
  118. if (claSolve.length <= 0 && els > 0) {
  119. cantSolve = true;
  120. break;
  121. }
  122. // 根据solve结果,取对应的值,然后做加减
  123. for (const s of claSolve) {
  124. const { schid, gender } = s;
  125. let midList = studentGroup[`${schid}-${gender}`];
  126. if (midList.length <= 0) continue;
  127. const head = _.head(midList);
  128. result[_id].push(head);
  129. midList = _.drop(midList);
  130. studentGroup[`${schid}-${gender}`] = midList;
  131. break; // 一次,一班只补一个的重要关键词
  132. }
  133. }
  134. } else {
  135. // 额外加人
  136. // 说明下这里为什么要ok:因为在上面的solve,强制以班级最少的人数为均分标准
  137. // 所以在班级人数不均等的情况下,出现某班均分完是正好的人数,剩下的班少人,但这时还得先把少人的班先补上才能均摊多的人
  138. // 为什么要说明,因为下面代码除了循环的 数组 外,都一样啊
  139. for (const c of ok) {
  140. // 需要计算出这个班补人的最优解
  141. const { _id } = c;
  142. const stuList = result[_id];
  143. const claSolve = this.getClassSolve(stuList);
  144. // 没有最优解=>没有班级有学生.人数甚至不满足把班级填满.需要手动安排
  145. if (claSolve.length <= 0 && els > 0) {
  146. cantSolve = true;
  147. break;
  148. }
  149. // 根据solve结果,取对应的值,然后做加减
  150. for (const s of claSolve) {
  151. const { schid, gender } = s;
  152. let midList = studentGroup[`${schid}-${gender}`];
  153. if (midList.length <= 0) continue;
  154. const head = _.head(midList);
  155. result[_id].push(head);
  156. midList = _.drop(midList);
  157. studentGroup[`${schid}-${gender}`] = midList;
  158. break;
  159. }
  160. }
  161. }
  162. // 重新计算人数,就是上面的els计算再执行一次
  163. els = _.flatten(Object.values(studentGroup)).length;
  164. }
  165. if (cantSolve) cantSolveNotice = true;
  166. }
  167. // for (const key in result) {
  168. // console.group(key);
  169. // const list = result[key];
  170. // const b = list.filter(f => f.gender === '男');
  171. // const g = list.filter(f => f.gender === '女');
  172. // console.log(list.length, b.length, g.length);
  173. // console.groupEnd();
  174. // }
  175. // 更新学生的班级
  176. const ckeys = Object.keys(result);
  177. for (const classid of ckeys) {
  178. await this.stumodel.updateMany({ _id: result[classid] }, { classid });
  179. }
  180. // 新添,给学生排序号
  181. const claList = await this.model.find({ termid });
  182. for (const cla of claList) {
  183. await this.ctx.service.student.arrangeNumber({ classid: cla._id });
  184. }
  185. if (cantSolveNotice) throw new BusinessError(ErrorCode.SERVICE_FAULT, '剩余人数无法继续自动安排,请手动分配班级');
  186. }
  187. /**
  188. * 获取某班补人的最优解列表
  189. * @param {Array} list 某班级的学生列表
  190. */
  191. getClassSolve(list) {
  192. // 因为按总人数的最优解已经均分过了,所以此处只计算,如何让该班平衡的最优解,不考虑整体了
  193. const schGroup = _.groupBy(list, 'schid');
  194. const midArr = [];
  195. for (const key in schGroup) {
  196. const midList = schGroup[key];
  197. const bl = midList.filter(f => f.gender === '男');
  198. midArr.push({ schid: key, gender: 'boy', number: bl.length });
  199. const gl = midList.filter(f => f.gender === '女');
  200. midArr.push({ schid: key, gender: 'girl', number: gl.length });
  201. }
  202. // 按 人数升序, 性别先男后女
  203. return _.orderBy(midArr, [ 'number', 'gender' ], [ 'asc', 'desc' ]);
  204. }
  205. /**
  206. * 检查班级是否达到人数
  207. * @param {Array} alreadyList 已分配的列表
  208. * @param {Array} classList 班级列表
  209. */
  210. checkClassStatus(alreadyList, classList) {
  211. let ok = []; // 已经满足人数的班级
  212. const not = []; // 未满足人数的班级
  213. for (const c of classList) {
  214. const { _id, number } = c;
  215. const nowNum = alreadyList[_id].length;
  216. if (nowNum < number) not.push(c);
  217. else ok.push(c);
  218. }
  219. // 需要排序,需要按上限人数排列,把人少的放上面
  220. ok = _.orderBy(ok, [ 'number' ], [ 'asc' ]);
  221. return { ok, not };
  222. }
  223. /**
  224. * 算整除和取余
  225. * @param {Any} num1 性别人数
  226. * @param {Any} num2 班级人数
  227. * @property {Number} res 整除结果
  228. * @property {Number} el 余数
  229. */
  230. numDivide(num1, num2) {
  231. num1 = _.isNaN(parseInt(num1)) ? 0 : parseInt(num1);
  232. num2 = _.isNaN(parseInt(num2)) ? 0 : parseInt(num2);
  233. const res = _.floor(num1 / num2, 0);
  234. const el = num1 % num2;
  235. return { res, el };
  236. }
  237. /**
  238. * 验证计算结果是否 不超过 最少人数班级的人数
  239. * @param {Object} solve 计算结果:key:${schid}-${gender}
  240. * @param {Number} number 最少人数班级的人数
  241. * @param {Number} classnum 班级数量,如果需要减人数的话,是要将每班的人数减1,-1就意味着要 - 班级数 *1;余数+班级数*1
  242. */
  243. checkSolve(solve, number, classnum) {
  244. let countClassNum = 0;
  245. let ns = [];
  246. for (const key in solve) {
  247. const { res = 0, el = 0 } = _.get(solve, key, {});
  248. countClassNum += res;
  249. ns.push({ res, el, key });
  250. }
  251. if (countClassNum <= number) return solve;
  252. do {
  253. ns = _.orderBy(ns, [ 'res' ], [ 'desc' ]);
  254. const head = _.head(ns);
  255. head.res--;
  256. head.el += classnum;
  257. ns[0] = head;
  258. countClassNum = ns.reduce((p, n) => p + n.res, 0);
  259. } while (countClassNum > number);
  260. for (const i of ns) {
  261. const { key, ...others } = i;
  262. solve[key] = { ...others };
  263. }
  264. return solve;
  265. }
  266. // 取得同样类型的学生
  267. async getstutype(_students, type) {
  268. const data = [];
  269. for (const stuid of _students) {
  270. const student = await this.stumodel.findById(stuid);
  271. if (student && student.type === type) {
  272. data.push(stuid);
  273. }
  274. }
  275. return data;
  276. }
  277. // 自动生成班级私有方法
  278. async autoclass(planid, termid) {
  279. // 将本期的学生都重新初始回无班级状态
  280. await this.stumodel.updateMany({ termid }, { classid: undefined });
  281. // 删除所有计划下的班级
  282. await this.model.deleteMany({ planid, termid });
  283. // 删除该期课表
  284. await this.lessmodel.deleteMany({ termid });
  285. // 根据批次id取得当前批次具体信息
  286. const res = await this.tmodel.findById(planid);
  287. if (!res) {
  288. throw new BusinessError(ErrorCode.DATA_NOT_EXIST, '全年计划信息不存在');
  289. }
  290. // 循环出所有班级进行添加操作
  291. const term = await res.termnum.id(termid);
  292. for (const batch of term.batchnum) {
  293. const classs = await batch.class;
  294. for (const cla of classs) {
  295. const newdata = { name: cla.name, number: cla.number, batchid: batch.id, termid: term.id, planid: res.id, type: cla.type };
  296. // // 查看班主任是否能上礼仪课 , headteacherid: cla.headteacherid
  297. // const is_ly = await this.heamodel.count({ _id: cla.headteacherid, islyteacher: '1' });
  298. // if (is_ly > 0)newdata.lyteacherid = cla.headteacherid;
  299. // 地点设置
  300. const rescla = await this.model.create(newdata);
  301. await this.toSetClassSetting({ classid: rescla._id });
  302. }
  303. }
  304. }
  305. // 根据传入的学生列表和班级id更新学生信息
  306. async studentup(classid, batchid, beforestu) {
  307. // 循环学生id
  308. for (const stuid of beforestu) {
  309. const student = await this.stumodel.findById(stuid);
  310. if (student) {
  311. student.classid = classid;
  312. student.batchid = batchid;
  313. await student.save();
  314. }
  315. }
  316. }
  317. // 自动分组
  318. async groupcreate(termid, batchid, classid) {
  319. const group = await this.gmodel.find({ termid, batchid, classid });
  320. if (group.length === 0) {
  321. for (let i = 1; i < 8; i++) {
  322. const name = i + '组';
  323. const newdata = { name, termid, batchid, classid };
  324. await this.gmodel.create(newdata);
  325. }
  326. }
  327. }
  328. // 根据传入的学生列表和班级id更新学生信息
  329. async studentupclass({ id }, data) {
  330. assert(id, '班级id为必填项');
  331. // 根据全年计划表id查出对应的全年计划详细信息
  332. const trainplan = await this.tmodel.findOne({ 'termnum.batchnum.class._id': ObjectId(id) });
  333. if (!trainplan) {
  334. throw new BusinessError(ErrorCode.DATA_NOT_EXIST, '全年计划信息不存在');
  335. }
  336. // 取得计划期批次信息
  337. let termid = '';
  338. let batchid = '';
  339. let classname = '';
  340. let class_ = {};
  341. for (const term of trainplan.termnum) {
  342. for (const batch of term.batchnum) {
  343. const _class = await batch.class.id(id);
  344. if (_class) {
  345. termid = term.id;
  346. batchid = batch.id;
  347. classname = _class.name;
  348. class_ = _class;
  349. break;
  350. }
  351. }
  352. }
  353. if (!class_) {
  354. throw new BusinessError(ErrorCode.DATA_NOT_EXIST, '班级信息不存在');
  355. }
  356. let classid_ = '';
  357. if (classname) {
  358. const cla_ = await this.model.findOne({ termid, batchid, name: classname });
  359. if (cla_) {
  360. classid_ = cla_.id;
  361. } else {
  362. const newdata = {
  363. name: class_.name,
  364. number: class_.number,
  365. batchid,
  366. termid,
  367. planid: trainplan.id,
  368. type: class_.type,
  369. headteacherid: class_.headteacherid,
  370. };
  371. const rescla = await this.model.create(newdata);
  372. if (rescla) {
  373. classid_ = rescla.id;
  374. }
  375. }
  376. }
  377. if (classid_) {
  378. // 循环学生id
  379. for (const stuid of data) {
  380. const student = await this.stumodel.findById(stuid);
  381. if (student) {
  382. student.classid = classid_;
  383. await student.save();
  384. }
  385. }
  386. }
  387. // 添加,给学生排序号
  388. await this.ctx.service.student.arrangeNumber({ classid: classid_ });
  389. // TODO 根据模板复制班级信息
  390. await this.toSetClassSetting({ classid: classid_ });
  391. }
  392. async notice(data) {
  393. for (const classid of data.classids) {
  394. // 根据班级id找到需要通知的班级
  395. const _class = await this.model.findById(classid);
  396. const { headteacherid } = _class;
  397. // 根据班级id找到对应的课程表
  398. const lesson = await this.lessmodel.findOne({ classid });
  399. if (lesson) {
  400. const lessons = lesson.lessons;
  401. const remark = '感谢您的使用';
  402. const date = await this.ctx.service.util.updatedate();
  403. const detail = '班级各项信息已确认,请注意查收';
  404. // 遍历班级授课教师发送通知
  405. for (const lessoninfo of lessons) {
  406. const teaid = lessoninfo.teaid;
  407. const _teacher = await this.umodel.findOne({ uid: teaid, type: '3' });
  408. if (_teacher) {
  409. const teaopenid = _teacher.openid;
  410. this.ctx.service.weixin.sendTemplateMsg(this.ctx.app.config.REVIEW_TEMPLATE_ID, teaopenid, '您有一个新的通知', detail, date, remark, classid);
  411. }
  412. }
  413. // 给班主任发送通知
  414. const _headteacher = await this.umodel.findOne({ uid: headteacherid, type: '1' });
  415. if (_headteacher) {
  416. const headteaopenid = _headteacher.openid;
  417. this.ctx.service.weixin.sendTemplateMsg(this.ctx.app.config.REVIEW_TEMPLATE_ID, headteaopenid, '您有一个新的通知', detail, date, remark, classid);
  418. }
  419. // 根据班级的期id查询对应的培训计划
  420. const trainplan = await this.tmodel.findOne({ 'termnum._id': _class.termid });
  421. const term = await trainplan.termnum.id(_class.termid);
  422. const batch = await term.batchnum.id(_class.batchid);
  423. const startdate = batch.startdate;
  424. const classname = _class.name;
  425. // 给班级所有学生发送邮件通知
  426. const students = await this.stumodel.find({ classid });
  427. for (const student of students) {
  428. const { email, name } = student;
  429. const subject = '吉林省高等学校毕业生就业指导中心通知';
  430. const text = name + '您好!\n欢迎参加由吉林省高等学校毕业生就业指导中心举办的“双困生培训会”。\n您所在的班级为:' + classname + '\n班级开课时间为:' + startdate;
  431. this.ctx.service.util.sendMail(email, subject, text);
  432. }
  433. }
  434. }
  435. }
  436. async uptea(data) {
  437. for (const _data of data) {
  438. const classInfo = await this.model.findById(_data.id);
  439. classInfo.headteacherid = _data.headteacherid;
  440. await classInfo.save();
  441. }
  442. }
  443. async query({ skip, limit, ...info }) {
  444. const classes = await this.model
  445. .find(info)
  446. .populate([
  447. {
  448. path: 'yclocationid',
  449. model: 'Location',
  450. select: 'name',
  451. },
  452. {
  453. path: 'kzjhlocationid',
  454. model: 'Location',
  455. select: 'name',
  456. },
  457. {
  458. path: 'kbyslocationid',
  459. model: 'Location',
  460. select: 'name',
  461. },
  462. {
  463. path: 'jslocationid',
  464. model: 'Location',
  465. select: 'name',
  466. },
  467. {
  468. path: 'headteacherid',
  469. model: 'Headteacher',
  470. select: 'name',
  471. },
  472. ])
  473. .skip(Number(skip))
  474. .limit(Number(limit));
  475. const data = [];
  476. let planids = classes.map(i => i.planid);
  477. planids = _.uniq(planids);
  478. const trainplan = await this.tmodel.find({ _id: { $in: planids } });
  479. for (const _class of classes) {
  480. let res = await this.setClassData(_class, trainplan);
  481. if (res) {
  482. res = this.setData(res);
  483. data.push(res);
  484. } else {
  485. data.push(_class);
  486. }
  487. }
  488. return data;
  489. }
  490. async fetch({ id }) {
  491. let classInfo = await this.model.findById(id).populate([
  492. {
  493. path: 'yclocationid',
  494. model: 'Location',
  495. select: 'name',
  496. },
  497. {
  498. path: 'kzjhlocationid',
  499. model: 'Location',
  500. select: 'name',
  501. },
  502. {
  503. path: 'kbyslocationid',
  504. model: 'Location',
  505. select: 'name',
  506. },
  507. {
  508. path: 'jslocationid',
  509. model: 'Location',
  510. select: 'name',
  511. },
  512. {
  513. path: 'headteacherid',
  514. model: 'Headteacher',
  515. select: 'name',
  516. },
  517. ]);
  518. const trainplan = await this.tmodel.findById(classInfo.planid);
  519. classInfo = await this.setClassData(classInfo, [ trainplan ]);
  520. classInfo = this.setData(classInfo);
  521. return classInfo;
  522. }
  523. // 整理数据,找礼仪教师
  524. async setClassData(cla, trainplan) {
  525. const { planid, termid, batchid } = cla;
  526. cla = JSON.parse(JSON.stringify(cla));
  527. const tpRes = trainplan.find(f => ObjectId(planid).equals(f._id));
  528. if (!tpRes) throw new BusinessError(ErrorCode.DATA_NOT_EXIST, '未找到班级的计划信息');
  529. const t = tpRes.termnum.id(termid);
  530. if (!t) throw new BusinessError(ErrorCode.DATA_NOT_EXIST, '未找到班级的期信息');
  531. const { term, batchnum } = t;
  532. if (!term) throw new BusinessError(ErrorCode.DATA_NOT_EXIST, '未找到班级的期信息');
  533. else cla.term = term;
  534. if (!batchnum) throw new BusinessError(ErrorCode.DATA_NOT_EXIST, '未找到班级的批次信息');
  535. const b = batchnum.id(batchid);
  536. if (!b) throw new BusinessError(ErrorCode.DATA_NOT_EXIST, '未找到班级的批次信息');
  537. const { batch, startdate, enddate } = b;
  538. if (batch) cla.batch = batch;
  539. if (startdate) cla.startdate = startdate;
  540. if (enddate) cla.enddate = enddate;
  541. // 礼仪教师
  542. if (cla.lyteacherid) {
  543. let res = await this.teamodel.findById(cla.lyteacherid);
  544. if (!res) res = await this.heamodel.findById(cla.lyteacherid);
  545. if (res) cla.lyteacher = res.name;
  546. }
  547. // 日间助教
  548. if (cla.rjteacherid) {
  549. let res = await this.teamodel.findById(cla.rjteacherid);
  550. if (!res) res = await this.heamodel.findById(cla.rjteacherid);
  551. if (res) cla.rjteacher = res.name;
  552. }
  553. return cla;
  554. }
  555. // 整理数据
  556. setData(cla) {
  557. const { headteacherid, yclocationid, kzjhlocationid, kbyslocationid, jslocationid } = cla;
  558. const arr = [];
  559. if (headteacherid && _.isObject(headteacherid)) arr.push({ headteacherid });
  560. if (yclocationid && _.isObject(yclocationid)) arr.push({ yclocationid });
  561. if (kzjhlocationid && _.isObject(kzjhlocationid)) arr.push({ kzjhlocationid });
  562. if (kbyslocationid && _.isObject(kbyslocationid)) arr.push({ kbyslocationid });
  563. if (jslocationid && _.isObject(jslocationid)) arr.push({ jslocationid });
  564. for (const kid of arr) {
  565. for (const key in kid) {
  566. if (kid.hasOwnProperty(key)) {
  567. const obj = kid[key];
  568. const { _id, name } = obj;
  569. const keynoids = key.split('id');
  570. cla[key] = _id;
  571. cla[_.get(keynoids, 0)] = name;
  572. }
  573. }
  574. }
  575. return cla;
  576. }
  577. async upclasses(data) {
  578. for (const _data of data) {
  579. await this.model.findByIdAndUpdate(_data.id, _data);
  580. }
  581. }
  582. async classinfo({ id: classid }) {
  583. const _classes = await this.model.findById(classid);
  584. // 班级信息
  585. const classes = _.cloneDeep(JSON.parse(JSON.stringify(_classes)));
  586. // 学生信息
  587. const students = await this.stumodel.find({ classid });
  588. // 所有用户信息
  589. const users = await this.umodel.find();
  590. if (students) {
  591. for (const stu of students) {
  592. const user = users.find(item => item.uid === stu.id);
  593. if (user && user.openid) {
  594. const _stu = _.cloneDeep(JSON.parse(JSON.stringify(stu)));
  595. _stu.hasuserinfo = '1';
  596. _.remove(students, stu);
  597. students.push(_stu);
  598. }
  599. }
  600. classes.students = students;
  601. }
  602. // 班主任信息
  603. let headteacher;
  604. if (classes.headteacherid) {
  605. headteacher = await this.heamodel.findById(classes.headteacherid);
  606. }
  607. // 礼仪课老师信息
  608. let lyteacher;
  609. if (classes.lyteacherid) {
  610. lyteacher = await this.heamodel.findById(classes.lyteacherid);
  611. if (!lyteacher) {
  612. lyteacher = await this.teamodel.findById(classes.lyteacherid);
  613. }
  614. }
  615. // 日间助教老师信息
  616. let rjteacher;
  617. if (classes.rjteacherid) {
  618. rjteacher = await this.heamodel.findById(classes.rjteacherid);
  619. if (!rjteacher) {
  620. rjteacher = await this.teamodel.findById(classes.rjteacherid);
  621. }
  622. }
  623. // 教课老师信息
  624. let teachers = [];
  625. const lessones = await this.lessmodel.findOne({ classid });
  626. if (lessones) {
  627. for (const lesson of lessones.lessons) {
  628. if (lesson.teaid) {
  629. const teacher = await this.teamodel.findById(lesson.teaid);
  630. teachers.push(teacher);
  631. }
  632. }
  633. }
  634. teachers.push(headteacher);
  635. teachers.push(lyteacher);
  636. teachers.push(rjteacher);
  637. teachers = _.uniq(_.compact(teachers));
  638. for (const tea of teachers) {
  639. const user = users.find(item => item.uid === tea.id);
  640. if (user && user.openid) {
  641. const _tea = _.cloneDeep(JSON.parse(JSON.stringify(tea)));
  642. _tea.hasuserinfo = '1';
  643. _.remove(teachers, tea);
  644. teachers.push(_tea);
  645. }
  646. }
  647. classes.teachers = teachers;
  648. return classes;
  649. }
  650. // 根据模板设置班级信息
  651. async toSetClassSetting({ classid }) {
  652. const setting = await this.ctx.model.Setting.findOne();
  653. if (!setting) throw new BusinessError(ErrorCode.DATA_NOT_EXIST, '未找到系统设置');
  654. const { template_term } = setting;
  655. if (!template_term) throw new BusinessError(ErrorCode.DATA_NOT_EXIST, '未找到班级模板设置');
  656. const templateList = await this.query({ termid: template_term });
  657. const tClass = await this.model.findById(classid);
  658. if (!tClass) throw new BusinessError(ErrorCode.DATA_NOT_EXIST, '班级不存在,无法复制设定的班级设置');
  659. const { name, termid, batchid } = tClass;
  660. const r = templateList.find(f => f.name === name);
  661. // 找到班主任全年计划
  662. const trainPlan = await this.tmodel.findById(tClass.planid);
  663. if (!trainPlan) throw new BusinessError(ErrorCode.DATA_NOT_EXIST, '未找到该班所在的年度计划信息');
  664. const tpt = trainPlan.termnum.id(tClass.termid);
  665. if (!tpt) throw new BusinessError(ErrorCode.DATA_NOT_EXIST, '未找到该班所在年度计划的期信息');
  666. const tpb = tpt.batchnum.id(tClass.batchid);
  667. if (!tpb) throw new BusinessError(ErrorCode.DATA_NOT_EXIST, '未找到该班所在年度计划的批次信息');
  668. if (!tpb.class) throw new BusinessError(ErrorCode.DATA_NOT_EXIST, '未找到该班所在年度计划批次下的班级');
  669. const tpc = tpb.class.find(f => f.name === tClass.name);
  670. if (!tpc) throw new BusinessError(ErrorCode.DATA_NOT_EXIST, '未找到该班所在年度计划的班级信息');
  671. const { headteacherid } = tpc;
  672. if (r) {
  673. // 说明这个是正常班,且从模板中找得到; 除了礼仪教师外,都复制过来
  674. const { jslocationid, kbyslocationid, kzjhlocationid, yclocationid } = r;
  675. if (!tClass.jslocation && jslocationid) tClass.jslocationid = jslocationid;
  676. if (!tClass.kbyslocationid && kbyslocationid) tClass.kbyslocationid = kbyslocationid;
  677. if (!tClass.kzjhlocationid && kzjhlocationid) tClass.kzjhlocationid = kzjhlocationid;
  678. if (!tClass.yclocationid && yclocationid) tClass.yclocationid = yclocationid;
  679. if (!tClass.headteacherid && headteacherid) {
  680. tClass.headteacherid = headteacherid;
  681. // 默认班主任为礼仪教师
  682. tClass.lyteacherid = headteacherid;
  683. }
  684. await tClass.save();
  685. } else {
  686. // 没找到,有可能是普通班,也有可能是非普通班
  687. // 找这个班级的同批次
  688. const tClassBatch = await this.query({ termid, batchid });
  689. const r = tClassBatch.find(f => ObjectId(tClass._id).equals(f._id));
  690. const ri = tClassBatch.findIndex(f => ObjectId(tClass._id).equals(f._id));
  691. // TODO 特殊班需要判断,如果没有就没有
  692. // if (r) throw new BusinessError(ErrorCode.DATA_NOT_EXIST, '无法确定班级批次排序');
  693. if (!r) {
  694. const { batch: tAllClassBatch } = r;
  695. const templateBatchList = templateList.filter(f => f.batch === tAllClassBatch);
  696. // 根据该班级所在批次的顺序,找到对应模板,然后复制
  697. const copyTemplate = templateBatchList[ri];
  698. const { jslocationid, kbyslocationid, kzjhlocationid, yclocationid } = copyTemplate;
  699. if (!tClass.jslocation && jslocationid) tClass.jslocationid = jslocationid;
  700. if (!tClass.kbyslocationid && kbyslocationid) tClass.kbyslocationid = kbyslocationid;
  701. if (!tClass.kzjhlocationid && kzjhlocationid) tClass.kzjhlocationid = kzjhlocationid;
  702. if (!tClass.yclocationid && yclocationid) tClass.yclocationid = yclocationid;
  703. if (!tClass.headteacherid && headteacherid) tClass.headteacherid = headteacherid;
  704. await tClass.save();
  705. }
  706. }
  707. }
  708. }
  709. module.exports = ClassService;