trainplan.js 33 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006
  1. 'use strict';
  2. const _ = require('lodash');
  3. const { CrudService } = require('naf-framework-mongoose/lib/service');
  4. const assert = require('assert');
  5. const { BusinessError, ErrorCode } = require('naf-core').Error;
  6. const XLSX = require('xlsx');
  7. const utils = require('../utils/utils.js');
  8. const moment = require('moment');
  9. const XLSXStyle = require('xlsx-style');
  10. class TrainplanService extends CrudService {
  11. constructor(ctx) {
  12. super(ctx, 'trainplan');
  13. this.model = this.ctx.model.Trainplan;
  14. this.clamodel = this.ctx.model.Class;
  15. this.umodel = this.ctx.model.User;
  16. this.smodel = this.ctx.model.School;
  17. this.tmodel = this.ctx.model.Teacher;
  18. this.stumodel = this.ctx.model.Student;
  19. this.schmodel = this.ctx.model.Schtime;
  20. this.lmmodel = this.ctx.model.Lessonmode;
  21. }
  22. async create(data) {
  23. const { planyearid, year, title } = data;
  24. assert(planyearid, '缺少大批次信息');
  25. assert(year, '缺少年度');
  26. assert(title, '缺少标题');
  27. const res = await this.model.create(data);
  28. console.log(res);
  29. let planid = '';
  30. if (res) planid = res._id;
  31. const schoolList = await this.smodel.find();
  32. const schtimeArr = [];
  33. for (const sch of schoolList) {
  34. const { code } = sch;
  35. const obj = { schid: code, year, planid };
  36. const schtimeres = await this.schmodel.create(obj);
  37. if (schtimeres) schtimeArr.push(schtimeres);
  38. }
  39. if (!schtimeArr.every(e => e)) { throw new BusinessError(ErrorCode.DATA_INVALID, '学校计划生成失败'); } else return res;
  40. }
  41. async update({ id }, data) {
  42. const trainplan = await this.model.findById(id);
  43. // 保存原数据
  44. const trainplanold = _.cloneDeep(trainplan);
  45. const { year, title, termnum, festivals, status, school } = data;
  46. if (year) {
  47. trainplan.year = year;
  48. }
  49. if (title) {
  50. trainplan.title = title;
  51. }
  52. if (termnum) {
  53. trainplan.termnum = termnum;
  54. }
  55. if (school) {
  56. trainplan.school = school;
  57. }
  58. if (festivals) {
  59. trainplan.festivals = festivals;
  60. }
  61. if (status === '1') {
  62. trainplan.status = status;
  63. }
  64. // 日历安排中添加课表信息,查询每种班级类型的课表,然后显示
  65. if (trainplan.termnum) {
  66. // trainplan.termnum =
  67. trainplan.termnum = await this.termGetLesson(trainplan.termnum);
  68. }
  69. // 如果培训计划状态改为发布,发送培训计划信息,并自动生成班级
  70. const res = await trainplan.save();
  71. if (res) {
  72. if (status === '1') {
  73. // 自动生成班级
  74. // await this.autoclass(res, trainplanold);
  75. // await this.autoclassNew(res, trainplanold);
  76. // 将生成的班级重新将班级排班名
  77. // await this.autoclassname(res);
  78. // 发送培训计划信息通知给相应人员
  79. // 查询所有入库的教师
  80. const teachers = await this.tmodel.find({ status: '4' });
  81. for (const teacher of teachers) {
  82. const teacherid = teacher._id;
  83. const _teacher = await this.umodel.findOne({
  84. uid: teacherid,
  85. type: '3',
  86. });
  87. const openid = _teacher.openid;
  88. const detail = trainplan.title + '已发布,请注意查收!';
  89. const date = await this.ctx.service.util.updatedate();
  90. const remark = '感谢您的使用';
  91. if (openid) {
  92. this.ctx.service.weixin.sendTemplateMsg(
  93. this.ctx.app.config.REVIEW_TEMPLATE_ID,
  94. openid,
  95. '您有一个新的通知',
  96. detail,
  97. date,
  98. remark
  99. );
  100. }
  101. }
  102. // 查询所有学校用户
  103. const schools = await this.umodel.find({ type: '2' });
  104. for (const school of schools) {
  105. const openid = school.openid;
  106. const detail = trainplan.title + '已发布,请注意查收!';
  107. const date = await this.ctx.service.util.updatedate();
  108. const remark = '感谢您的使用';
  109. if (openid) {
  110. this.ctx.service.weixin.sendTemplateMsg(
  111. this.ctx.app.config.REVIEW_TEMPLATE_ID,
  112. openid,
  113. '您有一个新的通知',
  114. detail,
  115. date,
  116. remark
  117. );
  118. }
  119. }
  120. }
  121. // 查哪个学校schtime表没有生成,就给生成了
  122. const schoolList = await this.smodel.find();
  123. for (const school of schoolList) {
  124. const r = await this.schmodel.findOne({ year, planid: id, schid: school.code });
  125. if (r) continue;
  126. const obj = { schid: school.code, year, planid: id };
  127. await this.schmodel.create(obj);
  128. }
  129. }
  130. return res;
  131. }
  132. async termGetLesson(termnum) {
  133. const lessonModelList = await this.lmmodel.find();
  134. for (const term of termnum) {
  135. for (const batch of term.batchnum) {
  136. const { class: classes, startdate, enddate } = batch;
  137. // 获取每批次下每个班的班级类型
  138. const typeList = _.uniq(classes.map(i => i.type));
  139. const h = _.head(typeList);
  140. if (!h) continue;
  141. const tem = lessonModelList.find(f => f.type === h);
  142. if (!tem) continue;
  143. let { lessons } = tem;
  144. if (!lessons) continue;
  145. lessons = JSON.parse(lessons);
  146. // 过滤出上课的时间段
  147. lessons = lessons.filter(f => {
  148. const keys = Object.keys(f).filter(f => f.includes('subid'));
  149. return keys.length > 0;
  150. });
  151. // 记录上课的时间
  152. const times = [];
  153. // 记录所有的科目
  154. let subject = [];
  155. lessons.map(i => {
  156. times.push(i.time);
  157. const keys = Object.keys(i);
  158. let arr = [];
  159. for (const key of keys) {
  160. if (key.match(/\d/g)) arr.push(_.head(key.match(/\d/g)));
  161. }
  162. arr = _.uniq(arr);
  163. for (const ai of arr) {
  164. if (i[`day${ai}subid`]) {
  165. subject.push({
  166. subname: i[`day${ai}`],
  167. subid: i[`day${ai}subid`],
  168. day: ai,
  169. });
  170. }
  171. }
  172. // console.log(arr);
  173. return i;
  174. });
  175. // 去重
  176. subject = _.uniqBy(subject, 'subid');
  177. // 获得天列表
  178. const dnum = moment(enddate).diff(moment(startdate), 'days') + 1;
  179. const dayList = [];
  180. for (let ind = 0; ind < dnum; ind++) {
  181. dayList.push(moment(startdate).add(ind, 'd').format('YYYY-MM-DD'));
  182. }
  183. // 将subject中的day换成日期
  184. for (const sub of subject) {
  185. sub.day = dayList[sub.day * 1 - 1];
  186. sub.time = times;
  187. }
  188. batch.lessons = subject;
  189. }
  190. }
  191. return termnum;
  192. }
  193. // 自动生成班级私有方法
  194. async autoclassNew(res) {
  195. // 删除所有计划下的班级
  196. await this.clamodel.deleteMany({ planid: res.id });
  197. // 循环出所有班级进行添加操作
  198. for (const term of res.termnum) {
  199. for (const batch of term.batchnum) {
  200. const classs = await batch.class;
  201. for (const cla of classs) {
  202. const newdata = {
  203. name: cla.name,
  204. number: cla.number,
  205. batchid: batch.id,
  206. termid: term.id,
  207. planid: res.id,
  208. type: cla.type,
  209. headteacherid: cla.headteacherid,
  210. };
  211. await this.clamodel.create(newdata);
  212. }
  213. }
  214. }
  215. }
  216. // 自动生成班级私有方法
  217. async autoclass(res, trainplanold) {
  218. // 首先比较当前数据和原数据的值是否有不同
  219. // 保存后所有期id
  220. const tremid_res = _.map(res.termnum, 'id');
  221. // 保存前所有期id
  222. const tremid_old = _.map(trainplanold.termnum, 'id');
  223. // 取得要删除的期id,进行班级中删除已删除期的班级
  224. const deltrem = _.difference(tremid_old, tremid_res);
  225. // 循环删除已经删除期的所有班级
  226. for (const elm of deltrem) {
  227. await this.clamodel.deleteMany({ termid: elm });
  228. }
  229. // 取得所有新加期id
  230. const addtrem = _.difference(tremid_res, tremid_old);
  231. // 清空后循环取得所有期进行批次操作
  232. const terms = res.termnum;
  233. for (const el of terms) {
  234. // 判断是否新加期
  235. if (_.indexOf(addtrem, el.id) !== -1) {
  236. // 循环当前新加期的批次列表,根据批次id和班级数生成班级信息
  237. const batchnums = el.batchnum;
  238. for (const batchnum of batchnums) {
  239. // 取得当前批次的班级数
  240. const classnum = batchnum.class;
  241. for (const cla of classnum) {
  242. const newdata = {
  243. name: cla.name,
  244. number: cla.number,
  245. batchid: batchnum.id,
  246. termid: el.id,
  247. planid: res.id,
  248. type: cla.type,
  249. };
  250. await this.clamodel.create(newdata);
  251. }
  252. }
  253. } else {
  254. // 不是新加期,更新期信息
  255. // 保存后所有期id
  256. const batchid_res = _.map(el.batchnum, 'id');
  257. // 保存前所有期id
  258. const batchid_old = _.map(
  259. trainplanold.termnum.id(el.id).batchnum,
  260. 'id'
  261. );
  262. // 取得要删除的期id,进行班级中删除已删除期的班级
  263. const delbatchs = _.difference(batchid_old, batchid_res);
  264. // 循环删除已经删除期的所有班级
  265. for (const delba of delbatchs) {
  266. await this.clamodel.deleteMany({ termid: el.id, batchid: delba });
  267. }
  268. // 取得所有新加期id
  269. const addbatch = _.difference(batchid_res, batchid_old);
  270. const batchnums = el.batchnum;
  271. for (const batchnum of batchnums) {
  272. // 取得当前批次是否有删除
  273. // 判断是否新加期
  274. if (_.indexOf(addbatch, batchnum.id) !== -1) {
  275. // 取得当前批次的班级数
  276. const classnum = batchnum.class;
  277. for (const cla of classnum) {
  278. const newdata = {
  279. name: cla.name,
  280. number: cla.number,
  281. batchid: batchnum.id,
  282. termid: el.id,
  283. planid: res.id,
  284. type: cla.type,
  285. };
  286. await this.clamodel.create(newdata);
  287. }
  288. } else {
  289. if (
  290. batchnum.class ===
  291. trainplanold.termnum.id(el.id).batchnum.id(batchnum.id).class
  292. ) {
  293. // 编辑只会针对班级人数进行修改。
  294. const _class = await this.clamodel.find({
  295. termid: el.id,
  296. batchid: batchnum.id,
  297. });
  298. if (_class.length !== 0) {
  299. for (const ee of _class) {
  300. ee.number = batchnum.number;
  301. await ee.save();
  302. }
  303. } else {
  304. const classnum = batchnum.class;
  305. for (const cla of classnum) {
  306. const newdata = {
  307. name: cla.name,
  308. number: cla.number,
  309. batchid: batchnum.id,
  310. termid: el.id,
  311. planid: res.id,
  312. type: cla.type,
  313. };
  314. await this.clamodel.create(newdata);
  315. }
  316. }
  317. } else {
  318. // 当班级数有更改时
  319. // 删除所有班级 并重新生成班级
  320. await this.clamodel.deleteMany({
  321. termid: el.id,
  322. batchid: batchnum.id,
  323. });
  324. const classnum = batchnum.class;
  325. for (const cla of classnum) {
  326. const newdata = {
  327. name: cla.name,
  328. number: cla.number,
  329. batchid: batchnum.id,
  330. termid: el.id,
  331. planid: res.id,
  332. type: cla.type,
  333. };
  334. await this.clamodel.create(newdata);
  335. }
  336. }
  337. }
  338. }
  339. }
  340. }
  341. }
  342. // // 将分好的班级重新编排名字
  343. // async autoclassname(res) {
  344. // // 取得所有期id
  345. // const tremid_res = _.map(res.termnum, 'id');
  346. // for (const termid of tremid_res) {
  347. // const classs = await this.clamodel.find({ planid: res.id, termid });
  348. // let i = 0;
  349. // for (const cla of classs) {
  350. // i = i + 1;
  351. // cla.name = i;
  352. // await cla.save();
  353. // }
  354. // }
  355. // }
  356. async exportExcel({ trainplanIds }) {
  357. const nowDate = new Date().getTime();
  358. const path =
  359. 'D:\\wwwroot\\service\\service-file\\upload\\train\\' + nowDate + '.xlsx';
  360. const respath =
  361. 'http://free.liaoningdoupo.com:80/files/train/' + nowDate + '.xlsx';
  362. const wb = {
  363. SheetNames: [],
  364. Sheets: {},
  365. };
  366. for (let i = 0; i < trainplanIds.length; i++) {
  367. // 批次期次都在这里面
  368. const trainplan = await this.model.findOne({ _id: trainplanIds[i] });
  369. // 这个计划下所有的学生
  370. const studentList = await this.stumodel.find({ planid: trainplanIds[i] });
  371. // 计划名称
  372. const trainplandName = trainplan.title;
  373. // 在计划中找到这个学生在哪期以及哪期下的哪批次
  374. for (const student of studentList) {
  375. student.isComming = utils.getIsNot(student.isComming);
  376. student.trainplandName = trainplandName;
  377. // 期次
  378. const term = trainplan.termnum.filter(term => {
  379. return term.id === student.termid;
  380. });
  381. if (term.length > 0) {
  382. student.termName = term[0].term;
  383. }
  384. // 批次
  385. if (term.length !== 0) {
  386. const batch = term[0].batchnum.filter(batch => {
  387. return batch.id === student.batchid;
  388. });
  389. if (batch.length > 0) {
  390. student.batchName = JSON.parse(JSON.stringify(batch[0])).name;
  391. }
  392. }
  393. student.is_fine = utils.getIsNot(student.is_fine);
  394. }
  395. const _headers = [
  396. { key: 'trainplandName', title: '计划标题' },
  397. { key: 'termName', title: '期次' },
  398. { key: 'batchName', title: '批次' },
  399. { key: 'school_name', title: '学校' },
  400. { key: 'faculty', title: '院系' },
  401. { key: 'major', title: '专业' },
  402. { key: 'name', title: '姓名' },
  403. { key: 'id_number', title: '身份证号' },
  404. { key: 'phone', title: '手机号' },
  405. { key: 'gender', title: '性别' },
  406. { key: 'nation', title: '民族' },
  407. { key: 'edua_level', title: '学历层次' },
  408. { key: 'edua_system', title: '学制' },
  409. { key: 'entry_year', title: '入学年份' },
  410. { key: 'finish_year', title: '毕业年份' },
  411. { key: 'school_job', title: '在校职务' },
  412. { key: 'qq', title: 'QQ号' },
  413. { key: 'email', title: '邮箱' },
  414. // { key: 'openid', title: '微信openid' },
  415. { key: 'family_place', title: '家庭位置' },
  416. { key: 'family_is_hard', title: '是否困难' },
  417. { key: 'have_grant', title: ' 是否获得过助学金' },
  418. // { key: 'job', title: '职务' },
  419. { key: 'bedroom', title: '寝室号' },
  420. { key: 'is_fine', title: '是否优秀' },
  421. { key: 'isComming', title: '是否签到' },
  422. { key: 'selfscore', title: '个人分' },
  423. { key: 'score', title: '总分' },
  424. ];
  425. // 需要打出的列表
  426. const _data = studentList;
  427. const headers = _headers
  428. .map(({ title }) => title)
  429. .map((v, i) =>
  430. Object.assign({}, { v, position: String.fromCharCode(65 + i) + 1 })
  431. )
  432. .reduce(
  433. (prev, next) =>
  434. Object.assign({}, prev, { [next.position]: { v: next.v } }),
  435. {}
  436. );
  437. const data = _data
  438. .map((v, i) =>
  439. _headers.map(({ key }, j) =>
  440. Object.assign(
  441. {},
  442. { v: v[key], position: String.fromCharCode(65 + j) + (i + 2) }
  443. )
  444. )
  445. )
  446. .reduce((prev, next) => prev.concat(next))
  447. .reduce(
  448. (prev, next) =>
  449. Object.assign({}, prev, { [next.position]: { v: next.v } }),
  450. {}
  451. );
  452. // 合并 headers 和 data
  453. const output = Object.assign({}, headers, data);
  454. // 获取所有单元格的位置
  455. const outputPos = Object.keys(output);
  456. // 计算出范围
  457. const ref = outputPos[0] + ':' + outputPos[outputPos.length - 1];
  458. // 构建 workbook 对象
  459. wb.SheetNames.push('sheet' + i);
  460. wb.Sheets['sheet' + i] = Object.assign({}, output, { '!ref': ref });
  461. }
  462. // 导出 Excel
  463. XLSX.writeFile(wb, path);
  464. return respath;
  465. }
  466. // 导出学校大表
  467. async exportSchool({ trainplanId }) {
  468. // 备注
  469. const remarks = [];
  470. // 期数
  471. let termCount = [];
  472. // 班级数
  473. const classCount = [];
  474. // 日期
  475. const studyTime = [];
  476. // 合并单元格坐标
  477. const colRows = [];
  478. // 列起始
  479. let colzb = 3;
  480. // 行起始
  481. const rowzb = 1;
  482. // const colRow = {
  483. // s: { c: 3, r: rowzb },
  484. // e: { c: 6, r: rowzb },
  485. // };
  486. // colRows.push(colRow);
  487. const shcoolList = [];
  488. // 计划表
  489. const trainplan = await this.model.findOne({ _id: trainplanId });
  490. // 学校报名表
  491. const schtime = await this.schmodel.find({ planid: trainplanId });
  492. // 期次
  493. const termnums = trainplan.termnum;
  494. // 学校学校数据
  495. // const schools = trainplan.school;
  496. const schools = await this.smodel.find({});
  497. // 组装学校数据
  498. for (let i = 0; i < schools.length; i++) {
  499. // 学校数据
  500. const shcool = [];
  501. // 序号
  502. shcool.push(i + 1);
  503. // 学校名
  504. shcool.push(schools[i].name);
  505. // 总人数
  506. shcool.push('');
  507. for (const termnum of termnums) {
  508. // 批次
  509. const batchnum = termnum.batchnum;
  510. // 期次所占的格(期占格)
  511. const qizhange = batchnum.length - 1;
  512. /**
  513. * 合并单元格元素(decode_range方法解析数据格式)
  514. {
  515. s: { //s start 开始
  516. c: 1,//cols 开始列
  517. r: 0 //rows 开始行
  518. },
  519. e: {//e end 结束
  520. c: 4,//cols 结束列
  521. r: 0 //rows 结束行
  522. }
  523. }
  524. */
  525. // 添加坐标
  526. const colRow = {
  527. s: { c: colzb, r: rowzb },
  528. e: { c: colzb + qizhange, r: rowzb },
  529. };
  530. // colzb为上一次终止,那么起始需+1,
  531. colzb = colzb + qizhange + 1;
  532. colRows.push(colRow);
  533. // 向其中加入空格,以备合并单元格使用
  534. const qi = [];
  535. qi.push(termnum.term);
  536. for (let index = 0; index < qizhange; index++) {
  537. qi.push('');
  538. }
  539. termCount = [ ...termCount, ...qi ];
  540. // 循环
  541. for (const batch of batchnum) {
  542. // 把班级数与日期放入数组中
  543. classCount.push(batch.class.length);
  544. let startDate = batch.startdate;
  545. startDate = startDate.substr(5, 2) + '.' + startDate.substr(8, 2);
  546. let endDate = batch.enddate;
  547. endDate = endDate.substr(5, 2) + '.' + endDate.substr(8, 2);
  548. studyTime.push(startDate + '-' + endDate);
  549. // 拿着batch的id去schtime表中的arrange中查remark,将结果存入remarks中即可完成备注数组
  550. let remark = '';
  551. for (const sch of schtime) {
  552. // 计划中学校的code=上报时的code
  553. if (schools[i].code === sch.schid) {
  554. for (const arrange of sch.arrange) {
  555. if (arrange.batchid === batch.id) {
  556. remark = arrange.remark;
  557. // 查到了退出即可因为是个数组
  558. // 总人数
  559. shcool.push(arrange.number);
  560. }
  561. }
  562. }
  563. }
  564. remarks.push(remark);
  565. }
  566. }
  567. shcoolList.push(shcool);
  568. }
  569. const wscols = [
  570. { wpx: 50 }, // 第一列宽度设置单位px
  571. ];
  572. let xuhao = [ XLSX.utils.decode_range('A1:A4') ];
  573. const xuexiao = [ XLSX.utils.decode_range('B1:B4') ];
  574. xuhao = [ ...xuhao, ...xuexiao, ...colRows ];
  575. // console.log(xuhao);
  576. const data = [];
  577. // 第一行
  578. const row0 = [ '序号', '学校名称', '备注' ].concat(remarks);
  579. data.push(row0);
  580. // 第二行
  581. const row1 = [ '', '', '期数' ].concat(termCount);
  582. data.push(row1);
  583. // 第三行
  584. const row2 = [ '', '', '班级数' ].concat(classCount);
  585. data.push(row2);
  586. // 第四行
  587. const row3 = [ '', '', '日期' ].concat(studyTime);
  588. data.push(row3);
  589. for (const shcoolL of shcoolList) {
  590. let count = 0;
  591. for (let i = 3; i < shcoolL.length; i++) {
  592. count += parseInt(shcoolL[i]);
  593. }
  594. // 计算出总人数,开始总认识默认的是'',这里赋值
  595. shcoolL[2] = count;
  596. data.push(shcoolL);
  597. }
  598. // ...以此类推即可
  599. /** 头部-行列信息*/
  600. const ws = XLSX.utils.aoa_to_sheet(data);
  601. // 构建 workbook 对象
  602. const nowDate = new Date().getTime();
  603. const path =
  604. 'D:\\wwwroot\\service\\service-file\\upload\\train\\' + nowDate + '.xlsx';
  605. const respath =
  606. 'http://free.liaoningdoupo.com:80/files/train/' + nowDate + '.xlsx';
  607. // 导出
  608. const wb = XLSX.utils.book_new();
  609. XLSX.utils.book_append_sheet(wb, ws, 'Sheet1');
  610. ws['!cols'] = wscols;
  611. // xuhao.push(XLSX.utils.decode_range('B1:D1')) // 测试数据 仓库1模拟数据
  612. ws['!merges'] = xuhao;
  613. // console.log(xuhao);
  614. XLSX.writeFile(wb, path);
  615. return respath;
  616. }
  617. // 导出学校大表
  618. async exportPlan({ trainplanId }) {
  619. const wscols = [
  620. { wpx: 50 }, // 第一列宽度设置单位px
  621. ];
  622. const monthList = [];
  623. // 月份合并单元格
  624. const colzb = 0;
  625. let rowzb = 1;
  626. // 头部合并单元格
  627. const coltb = 0;
  628. let rowtb = 0;
  629. // 人数合并单元格
  630. const colrs = 0;
  631. let rowrs = 1;
  632. // 人数数量合并单元格
  633. const colrssl = 0;
  634. let rowrssl = 3;
  635. // 班级数合并单元格
  636. const colbjs = 0;
  637. let rowbjs = 1;
  638. // 班级数数量合并单元格
  639. const colbjssl = 0;
  640. let rowbjssl = 3;
  641. // 数据
  642. const data = [];
  643. let colRowBJSSL = {};
  644. // 这里是头部颜色
  645. const tatleCell = { v: '', s: { fill: { fgColor: { rgb: '191970' } } } };
  646. // 坐标
  647. const tatleCellstyle = 0;
  648. let tatleRowstyle = 0;
  649. const styleTatle = [];
  650. // 这里是月份颜色
  651. const monthCell = [
  652. { v: '一月', s: { fill: { fgColor: { rgb: 'B0E2FF' } } } },
  653. { v: '二月', s: { fill: { fgColor: { rgb: 'B0E2FF' } } } },
  654. { v: '三月', s: { fill: { fgColor: { rgb: 'B0E2FF' } } } },
  655. { v: '四月', s: { fill: { fgColor: { rgb: 'B0E2FF' } } } },
  656. { v: '五月', s: { fill: { fgColor: { rgb: 'B0E2FF' } } } },
  657. { v: '六月', s: { fill: { fgColor: { rgb: 'B0E2FF' } } } },
  658. { v: '七月', s: { fill: { fgColor: { rgb: 'B0E2FF' } } } },
  659. { v: '八月', s: { fill: { fgColor: { rgb: 'B0E2FF' } } } },
  660. { v: '九月', s: { fill: { fgColor: { rgb: 'B0E2FF' } } } },
  661. { v: '十月', s: { fill: { fgColor: { rgb: 'B0E2FF' } } } },
  662. { v: '十一月', s: { fill: { fgColor: { rgb: 'B0E2FF' } } } },
  663. { v: '十二月', s: { fill: { fgColor: { rgb: 'B0E2FF' } } } },
  664. ];
  665. // 坐标
  666. const monthCellstyle = 0;
  667. let monthRowstyle = 1;
  668. const styleMonth = [];
  669. // 这里是假期颜色
  670. const festivalsCell = {
  671. v: '',
  672. s: { fill: { fgColor: { rgb: 'A2CD5A' } } },
  673. };
  674. // 坐标
  675. const festivalsCellstyle = 0;
  676. let festivalsRowstyle = 0;
  677. const stylefestivals = [];
  678. // 计划表
  679. const trainplan = await this.model.findOne({ _id: trainplanId });
  680. const termnum = trainplan.termnum;
  681. let classNum = 0;
  682. // 获取最大的班级数,也就是月份的高
  683. for (const term of termnum) {
  684. if (term.classnum > classNum) {
  685. classNum = parseInt(term.classnum);
  686. }
  687. }
  688. // 得到所有的节日日期的数组,然后循环时注意得到一个删除一个
  689. const festivals = trainplan.festivals;
  690. const festivalList = this.getfestivalList(festivals);
  691. console.log(utils.begindateEnddateSum('2020-01-25', '2020-02-05'));
  692. const termnumList = this.gettermnumList(termnum);
  693. // console.log(termnumList);
  694. // 得到所有班级的数组,然后循环时注意得到一个删除一个
  695. // 循环12个月,得到12个月以及每个月的数据
  696. for (let index = 1; index < 13; index++) {
  697. // 这里增加表格头部下标
  698. const tatleCells = XLSX.utils.encode_cell({
  699. c: tatleCellstyle,
  700. r: tatleRowstyle,
  701. });
  702. tatleRowstyle = tatleRowstyle + classNum + 3;
  703. styleTatle.push(tatleCells);
  704. // 这里是月份颜色
  705. const monthCells = XLSX.utils.encode_cell({
  706. c: monthCellstyle,
  707. r: monthRowstyle,
  708. });
  709. monthRowstyle = monthRowstyle + classNum + 3;
  710. styleMonth.push(monthCells);
  711. for (let j = 0; j < festivalList.length; j++) {
  712. const festival = festivalList[j];
  713. // console.log(festival);
  714. // 如果月份相同时才会增加
  715. const yue = parseInt(festival.substr(5, 2));
  716. const lie = parseInt(festival.substr(8, 2));
  717. if (index === yue) {
  718. // 这里是假期颜色这一列列7行都是这个颜色
  719. for (let k = 0; k < classNum; k++) {
  720. const festivalsCells = XLSX.utils.encode_cell({
  721. // 列
  722. c: festivalsCellstyle + lie,
  723. r: festivalsRowstyle + k + 3,
  724. });
  725. stylefestivals.push(festivalsCells);
  726. }
  727. }
  728. }
  729. festivalsRowstyle = festivalsRowstyle + classNum + 3;
  730. // console.log(stylefestivals);
  731. // 添加月份坐标
  732. const colRow = {
  733. s: { c: colzb, r: rowzb },
  734. // 保证留下7个空行,如果需要在上面加值,直接在下面加入即可第几空行加入就行
  735. e: { c: colzb, r: rowzb + classNum + 1 },
  736. };
  737. // rowzb为上一次终止,那么起始需+1,这里加3,代表头部+月份+星期,所以+3
  738. rowzb = rowzb + classNum + 3;
  739. monthList.push(colRow);
  740. // 添加头部坐标
  741. const colRowTB = {
  742. s: { c: coltb, r: rowtb },
  743. // 保证留下7个空行,如果需要在上面加值,直接在下面加入即可第几空行加入就行
  744. e: { c: coltb + 33, r: rowtb },
  745. };
  746. // rowzb为上一次终止,那么起始需+1,
  747. rowtb = rowtb + classNum + 3;
  748. monthList.push(colRowTB);
  749. // 添加人数坐标
  750. const colRowRS = {
  751. s: { c: colrs + 32, r: rowrs },
  752. // 保证留下7个空行,如果需要在上面加值,直接在下面加入即可第几空行加入就行
  753. e: { c: colrs + 32, r: rowrs + 1 },
  754. };
  755. // rowzb为上一次终止,那么起始需+1,
  756. rowrs = rowrs + classNum + 3;
  757. monthList.push(colRowRS);
  758. // 添加人数数量坐标
  759. const colRowRSSL = {
  760. s: { c: colrssl + 32, r: rowrssl },
  761. // 保证留下7个空行,如果需要在上面加值,直接在下面加入即可第几空行加入就行
  762. e: { c: colrssl + 32, r: rowrssl + classNum - 1 },
  763. };
  764. // rowzb为上一次终止,那么起始需+1,
  765. rowrssl = rowrssl + classNum + 3;
  766. monthList.push(colRowRSSL);
  767. // 添加班级数坐标
  768. const colRowBJS = {
  769. s: { c: colbjs + 33, r: rowbjs },
  770. // 保证留下7个空行,如果需要在上面加值,直接在下面加入即可第几空行加入就行
  771. e: { c: colbjs + 33, r: rowbjs + 1 },
  772. };
  773. // rowzb为上一次终止,那么起始需+1,
  774. rowbjs = rowbjs + classNum + 3;
  775. monthList.push(colRowBJS);
  776. // 添加班级数数量坐标
  777. colRowBJSSL = {
  778. s: { c: colbjssl + 33, r: rowbjssl },
  779. // 保证留下7个空行,如果需要在上面加值,直接在下面加入即可第几空行加入就行
  780. e: { c: colbjssl + 33, r: rowbjssl + classNum - 1 },
  781. };
  782. // rowzb为上一次终止,那么起始需+1,
  783. rowbjssl = rowbjssl + classNum + 3;
  784. monthList.push(colRowBJSSL);
  785. const resDate = this.makeCalendar(trainplan.year, index);
  786. data.push([ '' ]);
  787. data.push(
  788. [[ this.getBigMonth(index) + '月' ]]
  789. .concat(resDate.dlist)
  790. .concat([ '人数' ].concat([ '班级数' ]))
  791. );
  792. data.push([ '' ].concat(resDate.tlist));
  793. // 加列数组
  794. for (let i = 0; i < classNum; i++) {
  795. data.push('');
  796. }
  797. }
  798. // ...以此类推即可
  799. /** 头部-行列信息*/
  800. const ws = XLSX.utils.aoa_to_sheet(data);
  801. // 构建 workbook 对象
  802. const nowDate = new Date().getTime();
  803. const path =
  804. 'D:\\wwwroot\\service\\service-file\\upload\\train\\' + nowDate + '.xlsx';
  805. const respath =
  806. 'http://free.liaoningdoupo.com:80/files/train/' + nowDate + '.xlsx';
  807. // 导出
  808. const wb = XLSX.utils.book_new();
  809. XLSX.utils.book_append_sheet(wb, ws, 'Sheet1');
  810. ws['!cols'] = wscols;
  811. ws['!merges'] = monthList;
  812. // 头部赋值颜色需要计算出坐标
  813. for (const tatlezb of styleTatle) {
  814. ws[tatlezb] = tatleCell;
  815. }
  816. // 月份赋值颜色需要计算出坐标
  817. for (let index = 0; index < styleMonth.length; index++) {
  818. ws[styleMonth[index]] = monthCell[index];
  819. }
  820. // 假期赋值颜色需要计算出坐标
  821. for (const festivals of stylefestivals) {
  822. ws[festivals] = festivalsCell;
  823. }
  824. XLSXStyle.writeFile(wb, path);
  825. return respath;
  826. }
  827. // 获取批次日期列表
  828. gettermnumList(termnums) {
  829. const termnumList = [];
  830. for (const termnum of termnums) {
  831. termnum.term;
  832. termnum.classnum;
  833. for (const batchnum of termnum.batchnum) {
  834. batchnum.batch;
  835. batchnum.class.length;
  836. batchnum.startdate;
  837. batchnum.enddate;
  838. }
  839. }
  840. return termnumList;
  841. }
  842. // 获取节假日集合
  843. getfestivalList(festivals) {
  844. let dateList = [];
  845. for (let index = 0; index < festivals.length; index++) {
  846. dateList = [
  847. ...dateList,
  848. ...utils.begindateEnddateSum(
  849. festivals[index].begindate,
  850. festivals[index].finishdate
  851. ),
  852. ];
  853. }
  854. return dateList;
  855. }
  856. // 获取大月份传过来的值是以1月份开始的
  857. getBigMonth(index) {
  858. const monthBig = [
  859. '一',
  860. '二',
  861. '三',
  862. '四',
  863. '五',
  864. '六',
  865. '七',
  866. '八',
  867. '九',
  868. '十',
  869. '十一',
  870. '十二',
  871. ];
  872. return monthBig[index - 1];
  873. }
  874. // 获取这个月份的所有日期1~30号或者31或者28,或者29
  875. makeCalendar(year, month = 1, month0) {
  876. month0 = month;
  877. if (month * 1 < 10) month = '0' + month;
  878. // 获取这个月份的最大值
  879. const days = moment(year + '-' + month).daysInMonth();
  880. const dlist = this.getDayList(year, month, days, month0);
  881. while (dlist.dlist.length < 31) {
  882. dlist.dlist.push('');
  883. }
  884. return dlist;
  885. }
  886. // 获取这个月份的1-30号经过加工的
  887. getDayList(year, month, days, month0) {
  888. const dlist = [];
  889. const tlist = [];
  890. const all = {};
  891. for (let index = 0; index < days; index++) {
  892. dlist.push(
  893. month0 +
  894. '月' +
  895. moment(year + '-' + month)
  896. .add(index, 'days')
  897. .format('D') +
  898. '日'
  899. );
  900. let dayy = parseInt(index + 1);
  901. if (dayy * 1 < 10) dayy = '0' + dayy;
  902. tlist.push(this.getWeekDay(year + '-' + month + '-' + dayy));
  903. }
  904. all.dlist = dlist;
  905. all.tlist = tlist;
  906. return all;
  907. }
  908. // 获取星期几
  909. getWeekDay(datestr) {
  910. const weekday = moment(datestr).weekday();
  911. if (weekday || weekday === 0) {
  912. // console.log(weekday);
  913. const arr = [ '日', '一', '二', '三', '四', '五', '六' ];
  914. return '星期' + arr[weekday];
  915. }
  916. return '';
  917. }
  918. // async updateclass({ trainplanid, classid, rightHeader }) {
  919. // assert(trainplanid && classid && rightHeader, '缺少参数项');
  920. async updateclass({ trainplanid, termid, batchid, classid, rightHeader }) {
  921. assert(
  922. trainplanid && termid && batchid && classid && rightHeader,
  923. '缺少参数项'
  924. );
  925. // 根据全年计划表id查出对应的全年计划详细信息
  926. const trainplan = await this.model.findById(trainplanid);
  927. if (!trainplan) {
  928. throw new BusinessError(ErrorCode.DATA_NOT_EXIST, '全年计划信息不存在');
  929. }
  930. const term = trainplan.termnum.id(termid);
  931. if (!term) {
  932. throw new BusinessError(ErrorCode.DATA_NOT_EXIST, '期信息不存在');
  933. }
  934. const batch = term.batchnum.id(batchid);
  935. if (!batch) {
  936. throw new BusinessError(ErrorCode.DATA_NOT_EXIST, '批次信息不存在');
  937. }
  938. const class_ = await batch.class.id(classid);
  939. if (class_) {
  940. class_.headteacherid = rightHeader;
  941. }
  942. const res = await trainplan.save();
  943. if (res) {
  944. const cla_ = await this.clamodel.findOne({
  945. termid,
  946. batchid,
  947. name: class_.name,
  948. });
  949. if (cla_) {
  950. cla_.headteacherid = rightHeader;
  951. await cla_.save();
  952. }
  953. }
  954. return res;
  955. }
  956. async updatereteacher({ trainplanid, termid, reteacher }) {
  957. assert(trainplanid && termid && reteacher, '缺少参数项');
  958. // 根据全年计划表id查出对应的全年计划详细信息
  959. const trainplan = await this.model.findById(trainplanid);
  960. if (!trainplan) {
  961. throw new BusinessError(ErrorCode.DATA_NOT_EXIST, '全年计划信息不存在');
  962. }
  963. const term = await trainplan.termnum.id(termid);
  964. if (term) {
  965. term.reteacher = reteacher;
  966. }
  967. return await trainplan.save();
  968. }
  969. }
  970. module.exports = TrainplanService;