'use strict'; const _ = require('lodash'); const { CrudService } = require('naf-framework-mongoose/lib/service'); const assert = require('assert'); const { BusinessError, ErrorCode } = require('naf-core').Error; const XLSX = require('xlsx'); const utils = require('../utils/utils.js'); class TrainplanService extends CrudService { constructor(ctx) { super(ctx, 'trainplan'); this.model = this.ctx.model.Trainplan; this.clamodel = this.ctx.model.Class; this.umodel = this.ctx.model.User; this.smodel = this.ctx.model.School; this.tmodel = this.ctx.model.Teacher; this.stumodel = this.ctx.model.Student; this.schmodel = this.ctx.model.Schtime; } // async create(data) { // const terminfo = await data.termnum; // console.log(terminfo); // const { batchnum: { type, name, number }, term } = terminfo; // console.log(type); // if (type === 1) { // const classdata = { name, number, term, type }; // await this.clamodel.create(classdata); // } // if (type === 0) { // for (let i = 0; i < class; i++) { // const name = '第' + term + '期' + batch + '批次' + i + '班'; // const classdate = { name, number, term, type, newbatch }; // await this.clamodel.create(classdate); // } // } // return this.tpmodel.create(data); // } // } async update({ id }, data) { const trainplan = await this.model.findById(id); // 保存原数据 const trainplanold = _.cloneDeep(trainplan); const { year, title, termnum, festivals, status, school } = data; if (year) { trainplan.year = year; } if (title) { trainplan.title = title; } if (termnum) { trainplan.termnum = termnum; } if (school) { trainplan.school = school; } if (festivals) { trainplan.festivals = festivals; } if (status === '1') { trainplan.status = status; } // 如果培训计划状态改为发布,发送培训计划信息,并自动生成班级 const res = await trainplan.save(); if (res) { if (status === '1') { // 自动生成班级 // await this.autoclass(res, trainplanold); // await this.autoclassNew(res, trainplanold); // 将生成的班级重新将班级排班名 // await this.autoclassname(res); // 发送培训计划信息通知给相应人员 // 查询所有入库的教师 const teachers = await this.tmodel.find({ status: '4' }); for (const teacher of teachers) { const teacherid = teacher._id; const _teacher = await this.umodel.findOne({ uid: teacherid, type: '3', }); const openid = _teacher.openid; const detail = trainplan.title + '已发布,请注意查收!'; const date = await this.ctx.service.util.updatedate(); const remark = '感谢您的使用'; if (openid) { this.ctx.service.weixin.sendTemplateMsg( this.ctx.app.config.REVIEW_TEMPLATE_ID, openid, '您有一个新的通知', detail, date, remark ); } } // 查询所有学校用户 const schools = await this.umodel.find({ type: '2' }); for (const school of schools) { const openid = school.openid; const detail = trainplan.title + '已发布,请注意查收!'; const date = await this.ctx.service.util.updatedate(); const remark = '感谢您的使用'; if (openid) { this.ctx.service.weixin.sendTemplateMsg( this.ctx.app.config.REVIEW_TEMPLATE_ID, openid, '您有一个新的通知', detail, date, remark ); } } } } return res; } // 自动生成班级私有方法 async autoclassNew(res) { // 删除所有计划下的班级 await this.clamodel.deleteMany({ planid: res.id }); // 循环出所有班级进行添加操作 for (const term of res.termnum) { for (const batch of term.batchnum) { const classs = await batch.class; for (const cla of classs) { const newdata = { name: cla.name, number: cla.number, batchid: batch.id, termid: term.id, planid: res.id, type: cla.type, headteacherid: cla.headteacherid, }; await this.clamodel.create(newdata); } } } } // 自动生成班级私有方法 async autoclass(res, trainplanold) { // 首先比较当前数据和原数据的值是否有不同 // 保存后所有期id const tremid_res = _.map(res.termnum, 'id'); // 保存前所有期id const tremid_old = _.map(trainplanold.termnum, 'id'); // 取得要删除的期id,进行班级中删除已删除期的班级 const deltrem = _.difference(tremid_old, tremid_res); // 循环删除已经删除期的所有班级 for (const elm of deltrem) { await this.clamodel.deleteMany({ termid: elm }); } // 取得所有新加期id const addtrem = _.difference(tremid_res, tremid_old); // 清空后循环取得所有期进行批次操作 const terms = res.termnum; for (const el of terms) { // 判断是否新加期 if (_.indexOf(addtrem, el.id) !== -1) { // 循环当前新加期的批次列表,根据批次id和班级数生成班级信息 const batchnums = el.batchnum; for (const batchnum of batchnums) { // 取得当前批次的班级数 const classnum = batchnum.class; for (const cla of classnum) { const newdata = { name: cla.name, number: cla.number, batchid: batchnum.id, termid: el.id, planid: res.id, type: cla.type, }; await this.clamodel.create(newdata); } } } else { // 不是新加期,更新期信息 // 保存后所有期id const batchid_res = _.map(el.batchnum, 'id'); // 保存前所有期id const batchid_old = _.map( trainplanold.termnum.id(el.id).batchnum, 'id' ); // 取得要删除的期id,进行班级中删除已删除期的班级 const delbatchs = _.difference(batchid_old, batchid_res); // 循环删除已经删除期的所有班级 for (const delba of delbatchs) { await this.clamodel.deleteMany({ termid: el.id, batchid: delba }); } // 取得所有新加期id const addbatch = _.difference(batchid_res, batchid_old); const batchnums = el.batchnum; for (const batchnum of batchnums) { // 取得当前批次是否有删除 // 判断是否新加期 if (_.indexOf(addbatch, batchnum.id) !== -1) { // 取得当前批次的班级数 const classnum = batchnum.class; for (const cla of classnum) { const newdata = { name: cla.name, number: cla.number, batchid: batchnum.id, termid: el.id, planid: res.id, type: cla.type, }; await this.clamodel.create(newdata); } } else { if ( batchnum.class === trainplanold.termnum.id(el.id).batchnum.id(batchnum.id).class ) { // 编辑只会针对班级人数进行修改。 const _class = await this.clamodel.find({ termid: el.id, batchid: batchnum.id, }); if (_class.length !== 0) { for (const ee of _class) { ee.number = batchnum.number; await ee.save(); } } else { const classnum = batchnum.class; for (const cla of classnum) { const newdata = { name: cla.name, number: cla.number, batchid: batchnum.id, termid: el.id, planid: res.id, type: cla.type, }; await this.clamodel.create(newdata); } } } else { // 当班级数有更改时 // 删除所有班级 并重新生成班级 await this.clamodel.deleteMany({ termid: el.id, batchid: batchnum.id, }); const classnum = batchnum.class; for (const cla of classnum) { const newdata = { name: cla.name, number: cla.number, batchid: batchnum.id, termid: el.id, planid: res.id, type: cla.type, }; await this.clamodel.create(newdata); } } } } } } } // // 将分好的班级重新编排名字 // async autoclassname(res) { // // 取得所有期id // const tremid_res = _.map(res.termnum, 'id'); // for (const termid of tremid_res) { // const classs = await this.clamodel.find({ planid: res.id, termid }); // let i = 0; // for (const cla of classs) { // i = i + 1; // cla.name = i; // await cla.save(); // } // } // } async exportExcel({ trainplanIds }) { const nowDate = new Date().getTime(); const path = 'D:\\wwwroot\\service\\service-file\\upload\\train\\' + nowDate + '.xlsx'; const respath = 'http://free.liaoningdoupo.com:80/files/train/' + nowDate + '.xlsx'; const wb = { SheetNames: [], Sheets: {}, }; for (let i = 0; i < trainplanIds.length; i++) { // 批次期次都在这里面 const trainplan = await this.model.findOne({ _id: trainplanIds[i] }); // 这个计划下所有的学生 const studentList = await this.stumodel.find({ planid: trainplanIds[i] }); // 计划名称 const trainplandName = trainplan.title; // 在计划中找到这个学生在哪期以及哪期下的哪批次 for (const student of studentList) { student.isComming = utils.getIsNot(student.isComming); student.trainplandName = trainplandName; // 期次 const term = trainplan.termnum.filter(term => { return term.id === student.termid; }); if (term.length > 0) { student.termName = term[0].term; } // 批次 if (term.length !== 0) { const batch = term[0].batchnum.filter(batch => { return batch.id === student.batchid; }); if (batch.length > 0) { student.batchName = JSON.parse(JSON.stringify(batch[0])).name; } } student.is_fine = utils.getIsNot(student.is_fine); } const _headers = [ { key: 'trainplandName', title: '计划标题' }, { key: 'termName', title: '期次' }, { key: 'batchName', title: '批次' }, { key: 'school_name', title: '学校' }, { key: 'faculty', title: '院系' }, { key: 'major', title: '专业' }, { key: 'name', title: '姓名' }, { key: 'id_number', title: '身份证号' }, { key: 'phone', title: '手机号' }, { key: 'gender', title: '性别' }, { key: 'nation', title: '民族' }, { key: 'edua_level', title: '学历层次' }, { key: 'edua_system', title: '学制' }, { key: 'entry_year', title: '入学年份' }, { key: 'finish_year', title: '毕业年份' }, { key: 'school_job', title: '在校职务' }, { key: 'qq', title: 'QQ号' }, { key: 'email', title: '邮箱' }, // { key: 'openid', title: '微信openid' }, { key: 'family_place', title: '家庭位置' }, { key: 'family_is_hard', title: '是否困难' }, { key: 'have_grant', title: ' 是否获得过助学金' }, // { key: 'job', title: '职务' }, { key: 'bedroom', title: '寝室号' }, { key: 'is_fine', title: '是否优秀' }, { key: 'isComming', title: '是否签到' }, { key: 'selfscore', title: '个人分' }, { key: 'score', title: '总分' }, ]; // 需要打出的列表 const _data = studentList; const headers = _headers.map(({ title }) => title).map((v, i) => Object.assign({}, { v, position: String.fromCharCode(65 + i) + 1 }) ).reduce((prev, next) => Object.assign({}, prev, { [next.position]: { v: next.v } }), {} ); const data = _data.map((v, i) => _headers.map(({ key }, j) => Object.assign( {}, { v: v[key], position: String.fromCharCode(65 + j) + (i + 2) } ) ) ).reduce((prev, next) => prev.concat(next)).reduce( (prev, next) => Object.assign({}, prev, { [next.position]: { v: next.v } }), {} ); // 合并 headers 和 data const output = Object.assign({}, headers, data); // 获取所有单元格的位置 const outputPos = Object.keys(output); // 计算出范围 const ref = outputPos[0] + ':' + outputPos[outputPos.length - 1]; // 构建 workbook 对象 wb.SheetNames.push('sheet' + i); wb.Sheets['sheet' + i] = Object.assign({}, output, { '!ref': ref }); } // 导出 Excel XLSX.writeFile(wb, path); return respath; } // 导出学校大表 async exportSchool({ trainplanId }) { // 备注 const remarks = []; // 期数 let termCount = []; // 班级数 const classCount = []; // 日期 const studyTime = []; // 合并单元格坐标 const colRows = []; // 列起始 let colzb = 3; // 行起始 const rowzb = 1; // const colRow = { // s: { c: 3, r: rowzb }, // e: { c: 6, r: rowzb }, // }; // colRows.push(colRow); const shcoolList = []; // 计划表 const trainplan = await this.model.findOne({ _id: trainplanId }); // 学校报名表 const schtime = await this.schmodel.find({ planid: trainplanId }); // 期次 const termnums = trainplan.termnum; // 学校学校数据 // const schools = trainplan.school; const schools = await this.smodel.find({}); // 组装学校数据 for (let i = 0; i < schools.length; i++) { // 学校数据 const shcool = []; // 序号 shcool.push(i + 1); // 学校名 shcool.push(schools[i].name); // 总人数 shcool.push(''); for (const termnum of termnums) { // 批次 const batchnum = termnum.batchnum; // 期次所占的格(期占格) const qizhange = batchnum.length - 1; /** * 合并单元格元素(decode_range方法解析数据格式) { s: { //s start 开始 c: 1,//cols 开始列 r: 0 //rows 开始行 }, e: {//e end 结束 c: 4,//cols 结束列 r: 0 //rows 结束行 } } */ // 添加坐标 const colRow = { s: { c: colzb, r: rowzb }, e: { c: colzb + qizhange, r: rowzb }, }; // colzb为上一次终止,那么起始需+1, colzb = colzb + qizhange + 1; colRows.push(colRow); // 向其中加入空格,以备合并单元格使用 const qi = []; qi.push(termnum.term); for (let index = 0; index < qizhange; index++) { qi.push(''); } termCount = [ ...termCount, ...qi ]; // 循环 for (const batch of batchnum) { // 把班级数与日期放入数组中 classCount.push(batch.class.length); let startDate = batch.startdate; startDate = startDate.substr(5, 2) + '.' + startDate.substr(8, 2); let endDate = batch.enddate; endDate = endDate.substr(5, 2) + '.' + endDate.substr(8, 2); studyTime.push(startDate + '-' + endDate); // 拿着batch的id去schtime表中的arrange中查remark,将结果存入remarks中即可完成备注数组 let remark = ''; for (const sch of schtime) { // 计划中学校的code=上报时的code if (schools[i].code === sch.schid) { for (const arrange of sch.arrange) { if (arrange.batchid === batch.id) { remark = arrange.remark; // 查到了退出即可因为是个数组 // 总人数 shcool.push(arrange.number); } } } } remarks.push(remark); } } shcoolList.push(shcool); } const wscols = [ { wpx: 50 }, // 第一列宽度设置单位px ]; let xuhao = [ XLSX.utils.decode_range('A1:A4') ]; const xuexiao = [ XLSX.utils.decode_range('B1:B4') ]; xuhao = [ ...xuhao, ...xuexiao, ...colRows ]; // console.log(xuhao); const data = []; // 第一行 const row0 = [ '序号', '学校名称', '备注' ].concat(remarks); data.push(row0); // 第二行 const row1 = [ '', '', '期数' ].concat(termCount); data.push(row1); // 第三行 const row2 = [ '', '', '班级数' ].concat(classCount); data.push(row2); // 第四行 const row3 = [ '', '', '日期' ].concat(studyTime); data.push(row3); for (const shcoolL of shcoolList) { let count = 0; for (let i = 3; i < shcoolL.length; i++) { count += parseInt(shcoolL[i]); } // 计算出总人数,开始总认识默认的是'',这里赋值 shcoolL[2] = count; data.push(shcoolL); } // ...以此类推即可 /** 头部-行列信息*/ const ws = XLSX.utils.aoa_to_sheet(data); // 构建 workbook 对象 const nowDate = new Date().getTime(); const path = 'D:\\wwwroot\\service\\service-file\\upload\\train\\' + nowDate + '.xlsx'; const respath = 'http://free.liaoningdoupo.com:80/files/train/' + nowDate + '.xlsx'; // 导出 const wb = XLSX.utils.book_new(); XLSX.utils.book_append_sheet(wb, ws, 'Sheet1'); ws['!cols'] = wscols; // xuhao.push(XLSX.utils.decode_range('B1:D1')) // 测试数据 仓库1模拟数据 ws['!merges'] = xuhao; // console.log(xuhao); XLSX.writeFile(wb, path); return respath; // // 批次期次都在这里面 // const trainplan = await this.model.find({ _id: trainplanId }); // const _headers = [ // { key: 'title', title: '计划标题' }, // { key: 'year', title: '年度' }, // ]; // // 需要打出的列表 // const _data = trainplan; // const headers = _headers.map(({ title }) => // title).map((v, i) => // Object.assign({}, { v, position: String.fromCharCode(65 + i) + 1 }) // ).reduce((prev, next) => // Object.assign({}, prev, { [next.position]: { v: next.v } }), // {} // ); // console.log('--------------------headers'); // console.log(headers); // const data = _data.map((v, i) => // _headers.map(({ key }, j) => // Object.assign( // {}, // { v: v[key], position: String.fromCharCode(65 + j) + (i + 2) } // ) // ) // ).reduce((prev, next) => // prev.concat(next)).reduce((prev, next) => // Object.assign({}, prev, { [next.position]: { v: next.v } }), // {} // ); // console.log('--------------------data'); // console.log(data); // // 合并 headers 和 data // const output = Object.assign({}, headers, data); // console.log('----------output'); // console.log(output); // // 获取所有单元格的位置 // const outputPos = Object.keys(output); // console.log('--------------------outputPos'); // console.log(outputPos); // // 计算出范围 // const ref = outputPos[0] + ':' + outputPos[outputPos.length - 1]; // console.log('--------------------ref'); // console.log(ref); // // 构建 workbook 对象 // const nowDate = new Date().getTime(); // const path = // 'D:\\wwwroot\\service\\service-file\\upload\\train\\' + // nowDate + // '.xlsx'; // const respath = // 'http://free.liaoningdoupo.com:80/files/train/' + nowDate + '.xlsx'; // const wb = { // SheetNames: [ 'sheet0' ], // Sheets: { sheet0: Object.assign({}, output, { '!ref': ref }) }, // // 导出 Excel // }; // console.log('----------wb'); // console.log(wb); // XLSX.writeFile(wb, path); // return respath; } async updateclass({ trainplanid, termid, batchid, classid, rightHeader }) { assert(trainplanid && termid && batchid && classid && rightHeader, '缺少参数项'); // 根据全年计划表id查出对应的全年计划详细信息 const trainplan = await this.model.findById(trainplanid); if (!trainplan) { throw new BusinessError(ErrorCode.DATA_NOT_EXIST, '全年计划信息不存在'); } const term = trainplan.termnum.id(termid); if (!term) { throw new BusinessError(ErrorCode.DATA_NOT_EXIST, '期信息不存在'); } const batch = term.batchnum.id(batchid); if (!batch) { throw new BusinessError(ErrorCode.DATA_NOT_EXIST, '批次信息不存在'); } const class_ = await batch.class.id(classid); if (class_) { class_.headteacherid = rightHeader; } const res = await trainplan.save(); if (res) { const cla_ = await this.clamodel.findOne({ termid, batchid, name: class_.name }); if (cla_) { cla_.headteacherid = rightHeader; await cla_.save(); } } return res; } async updatereteacher({ trainplanid, termid, reteacher }) { assert(trainplanid && termid && reteacher, '缺少参数项'); // 根据全年计划表id查出对应的全年计划详细信息 const trainplan = await this.model.findById(trainplanid); if (!trainplan) { throw new BusinessError(ErrorCode.DATA_NOT_EXIST, '全年计划信息不存在'); } const term = await trainplan.termnum.id(termid); if (term) { term.reteacher = reteacher; } return await trainplan.save(); } } module.exports = TrainplanService;