'use strict'; const assert = require('assert'); const _ = require('lodash'); const moment = require('moment'); const { ObjectId } = require('mongoose').Types; const { CrudService } = require('naf-framework-mongoose/lib/service'); const { BusinessError, ErrorCode } = require('naf-core').Error; class QuestionnaireService extends CrudService { constructor(ctx) { super(ctx, 'qusetionnaire'); this.questionnairemodel = this.ctx.model.Questionnaire; this.questionmodel = this.ctx.model.Question; const { baseUrl } = _.get(this.ctx.app.config, 'mission'); if (baseUrl) this.missionBase = baseUrl; } // 插入问卷 async create(data) { const { name, num, type, question } = data; assert(name, '问卷名称不能为空'); assert(num, '问卷序号不能为空'); assert(type, '问卷类型不能为空'); const quedata = []; for (const elm of question) { const ques = await this.questionmodel.findById(elm); if (ques) { quedata.push(ques); } } data.question = quedata; return await this.questionnairemodel.create(data); } // 根据id删除问卷 async delete({ id }) { await this.questionnairemodel.findByIdAndDelete(id); return 'deleted'; } // 根据id更新问卷信息 async update({ id }, data) { const { name, num, type, question, status } = data; const questionnaire = await this.questionnairemodel.findById(id); if (name) { questionnaire.name = name; } if (num) { questionnaire.num = num; } if (type) { questionnaire.type = type; } if (status) { questionnaire.status = status; } if (question) { const quedata = []; for (const elm of question) { if (elm && !_.isObject(elm)) { const ques = await this.questionmodel.findById(elm); if (ques) { quedata.push(ques); } } else if (elm) quedata.push(elm); } questionnaire.question = quedata; } return await questionnaire.save(); } // 查询 async query({ skip, limit, ...num }) { const total = await this.questionnairemodel.count(num); const data = await this.questionnairemodel.find(num).skip(Number(skip)).limit(Number(limit)); const result = { total, data }; return result; } // 查询详情 async show({ id }) { const questionnaire = await this.questionnairemodel.findById(id); return questionnaire; } // 建立任务 async toExport(body) { const { range, questionnaireid } = body; const questionnaire = await this.questionnairemodel.findById(questionnaireid); if (!questionnaire) { throw new BusinessError(ErrorCode.DATA_NOT_EXIST, '未找到问卷信息'); } const fn = await this.toSetFileName(questionnaire.name, range); const data = { title: fn, params: { project: 'center', service: 'questionnaire', method: 'export', body, }, }; if (this.missionBase) { const url = `${this.missionBase}/api/mission`; const res = await this.ctx.curl(url, { method: 'post', headers: { 'content-type': 'application/json', }, data, dataType: 'json', }); if (res.status !== 200 || res.data.errcode !== 0) { throw new BusinessError(ErrorCode.SERVICE_FAULT, '创建任务失败'); } } else { throw new BusinessError(ErrorCode.SERVICE_FAULT, '未找到任务项目设置'); } } /** * 导出问卷 * @param {Object} { range, direction, questionnaireid, modelList, missionid } 数据集合 * @property Object range 学生的查询范围 * @property String direction horizontal横向/vertical纵向 * @property String questionnaireid 问卷id * @property Array modelList 要导出的字段,学生和问卷都在这里, 学生的table:student, 问卷的table:questionnaire * @property String missionid 任务id */ async export({ range, direction, questionnaireid, modelList, missionid }) { assert(missionid, '缺少任务信息,无法执行任务'); try { // 将期批班转换 modelList = modelList.map(i => { const { model } = i; if (model === 'termid') i.model = 'termname'; else if (model === 'batchid') i.model = 'batchname'; else if (model === 'classid') i.model = 'classname'; return i; }); // 获取问卷 const questionnaire = await this.questionnairemodel.findById(questionnaireid); if (!questionnaire) { throw new BusinessError(ErrorCode.DATA_NOT_EXIST, '未找到问卷信息'); } let fn = await this.toSetFileName(questionnaire.name, range); // this.ctx.service.util.updateProcess(missionid, '25'); // 修改条件,termid变成数组了,需要一个一个查出来 const { planid, batchid, classid } = range; let { termid } = range; const queryObject = {}; if (planid) queryObject.planid = planid; if (batchid) queryObject.batchid = batchid; if (classid) queryObject.classid = classid; let count = 0; let dp; const qkeys = Object.keys(range); if (qkeys.length === 2 && termid.length <= 0) { // 说明只有planid const plan = await this.ctx.model.Trainplan.findOne({ _id: ObjectId(planid) }); if (!plan) throw new BusinessError(ErrorCode.DATA_NOT_EXIST, '未找到指定年度计划'); termid = plan.termnum.map(i => JSON.parse(JSON.stringify(i._id))); } for (const t of termid) { queryObject.termid = t; let data = await this.toGetData(queryObject, questionnaireid, direction, modelList); if (!data) continue; if (count !== 0 && direction === 'horizontal') data.shift(); if (count !== 0 && direction === 'vertical') { data = data.map(i => { i.shift(); return i; }); } const { downloadPath, fn: newFileName } = await this.ctx.service.util.toAsyncExcel(data, fn, dp, direction); dp = downloadPath; fn = newFileName; count++; } this.ctx.service.util.updateProcess(missionid, '100', '2', { uri: dp, }); } catch (error) { console.log(error); this.ctx.service.util.updateProcess(missionid, undefined, '3'); } } /** * 获取学生与学生回答的问卷 * @param {Object} condition 查询学生和学生回答问卷的条件 * @param {String} questionnaireid 问卷id * @param {String} direction 方向 * @param {Array} modelList 导出字段 */ async toGetData(condition, questionnaireid, direction, modelList) { // 获取学生 let studentList = await this.ctx.service.student.query(condition); if (studentList.length <= 0) { this.ctx.logger.error(`${JSON.stringify(condition)}条件下,未找到任何信息`); return; // throw new BusinessError(ErrorCode.DATA_NOT_EXIST, '未找到任何学生信息'); } studentList = JSON.parse(JSON.stringify(studentList)); // 再获取问卷答案 const questAnswerList = await this.ctx.model.Uploadquestion.find({ ...condition, questionnaireid, }); if (!questAnswerList) { this.ctx.logger.error(`${JSON.stringify(condition)}&${questionnaireid}条件下,未找到任何完成的问卷`); return; } let res = []; let data = []; if (direction === 'horizontal') { const head = this.getHead(direction, modelList, studentList.length); data = this.horizontalSetData(studentList, questAnswerList, modelList); res.push(head.map(i => i.header)); for (const d of data) { const mid = []; for (const h of head) { const { key } = h; mid.push(_.get(d, key, '')); } res.push(mid); } } else if (direction === 'vertical') { data = this.verticaSetData(studentList, questAnswerList, modelList); res = data.map(i => Object.values(i)); } else { throw new BusinessError(ErrorCode.DATA_NOT_EXIST, '未找到excel的表头排列方式'); } return res; } /** * 获得导出文件的文件名 * @param {String} questName 问卷名 * @param {Object} range 范围(计划-期-批-班) * @return {String} fn 文件名 */ async toSetFileName(questName, range) { let fn = `-${questName}`; const { planid, termid, batchid, classid } = range; if (classid) { const cla = await this.ctx.service.class.fetch({ id: classid }); if (cla) { const { name, term } = cla; if (name) fn = `${name.includes('班') ? name : `${name}班`}${fn}`; if (term) fn = `第${term}期${fn}`; } } else if (batchid) { const tid = _.head(termid); const obj = await this.toGetFn(tid, batchid); if (obj) { const { term, batch } = obj; if (batch) fn = `第${batch}批${fn}`; if (term) fn = `第${term}期${fn}`; } } else if (termid) { if (termid.length === 1) { const obj = await this.toGetFn(_.head(termid)); if (obj) { const { term } = obj; if (term) fn = `第${term}期${fn}`; } } else { let tStr = ''; for (let i = 0; i < termid.length; i++) { const tid = termid[i]; const obj = await this.toGetFn(tid); if (obj) { const { term } = obj; if (term) { if (i === 0) { tStr += `${term}期`; } else { tStr += `,${term}期`; } } } } fn = `${tStr}${fn}`; } } else { const trainPlan = await this.ctx.model.Trainplan.findById(planid); if (trainPlan) { const { title } = trainPlan; if (title) fn = `${title}${fn}`; } } return fn; } /** * 获取文件的期/期批 * @param {String} termid 期id * @param {String} batchid 批id */ async toGetFn(termid, batchid) { const trainPlan = await this.ctx.model.Trainplan.findOne({ 'termnum._id': ObjectId(termid), }); if (!trainPlan) { throw new BusinessError(ErrorCode.DATA_NOT_EXIST, '未找到年度计划信息'); } const { termnum } = trainPlan; if (!termnum) { throw new BusinessError(ErrorCode.DATA_NOT_EXIST, '未找到年度计划的期信息'); } const obj = {}; if (termid) { const term = termnum.id(termid); if (term) obj.term = term.term; if (batchid) { const { batchnum } = term; const batch = batchnum.id(batchid); if (batch) obj.batch = batch.batch; } } return obj; } /** * 取出对应方向的表头 * @param {String} type 横纵向类型 * @param {Array} modelList 导出字段 * @param {Number} studentList count的学生数量 */ getHead(type, modelList, studentList) { let head = []; if (type === 'horizontal') { // 横向头 head = modelList.map(i => { const { zh, model, _id } = i; const headObj = { header: zh }; if (model) headObj.key = model; else if (_id) headObj.key = _id; headObj.width = 20; return headObj; }); } else { // 纵向头 const head = [{ key: 'c', width: 20 }]; for (let i = 1; i <= studentList.length; i++) { head.push({ key: `c${i}`, width: 20 }); } } return head; } /** * 横向组织数据 * @param {Array} studentList 学生列表 * @param {Array} questAnswerList 学生回答问卷列表 * @param {Array} modelList 需要导出的字段 */ horizontalSetData(studentList, questAnswerList, modelList) { // 组织数据需要按照modelList的顺序进行整理 const data = []; for (const stu of studentList) { const obj = {}; for (const m of modelList) { const { model, table, _id } = m; if (table === 'student') { const prop = _.get(stu, model, ''); obj[model] = prop; } else if (table === 'questionnaire') { const qar = questAnswerList.find(f => f.studentid === stu._id); // 没找到该学生的问卷,下一个 if (!qar) continue; const { answers } = qar; // 回答的数据不存在/不是数组,数据格式不对,下一个 if (!(answers && _.isArray(answers))) continue; const ar = answers.find(f => f.questionid === _id); // 没找到答案,下个 if (!ar) continue; const { answer } = ar; // 没有答案,下个 if (!answer) continue; obj[_id] = _.trim(answer, '"'); } } data.push(obj); } // const obj = {}; // if (head) obj.head = head; // if (data) obj.data = data; return data; } /** * 纵向组织数据 * @param {Array} studentList 学生列表 * @param {Array} questAnswerList 学生回答问卷列表 * @param {Array} modelList 需要导出的字段 */ verticaSetData(studentList, questAnswerList, modelList) { // 整理数据 const data = []; for (const m of modelList) { const { table, model, _id, zh } = m; const obj = { c: zh }; if (table === 'student') { for (let i = 0; i < studentList.length; i++) { const stu = studentList[i]; const value = _.get(stu, model, ''); if (value) obj[`c${i + 1}`] = value; } } else if (table === 'questionnaire') { for (let i = 0; i < studentList.length; i++) { const stu = studentList[i]; const qar = questAnswerList.find(f => f.studentid === stu._id); // 没找到该学生的问卷,下一个 if (!qar) continue; const { answers } = qar; // 回答的数据不存在/不是数组,数据格式不对,下一个 if (!(answers && _.isArray(answers))) continue; const ar = answers.find(f => f.questionid === _id); // 没找到答案,下个 if (!ar) continue; const { answer } = ar; // 没有答案,下个 if (!answer) continue; obj[`c${i + 1}`] = _.trim(answer, '"'); } } data.push(obj); } return data; } } module.exports = QuestionnaireService;