questionnaire.js 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331
  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 QuestionnaireService extends CrudService {
  8. constructor(ctx) {
  9. super(ctx, 'qusetionnaire');
  10. this.questionnairemodel = this.ctx.model.Questionnaire;
  11. this.questionmodel = this.ctx.model.Question;
  12. }
  13. // 插入问卷
  14. async create(data) {
  15. const { name, num, type, question } = data;
  16. assert(name, '问卷名称不能为空');
  17. assert(num, '问卷序号不能为空');
  18. assert(type, '问卷类型不能为空');
  19. const quedata = [];
  20. for (const elm of question) {
  21. const ques = await this.questionmodel.findById(elm);
  22. if (ques) {
  23. quedata.push(ques);
  24. }
  25. }
  26. data.question = quedata;
  27. return await this.questionnairemodel.create(data);
  28. }
  29. // 根据id删除问卷
  30. async delete({ id }) {
  31. await this.questionnairemodel.findByIdAndDelete(id);
  32. return 'deleted';
  33. }
  34. // 根据id更新问卷信息
  35. async update({ id }, data) {
  36. const { name, num, type, question } = data;
  37. const questionnaire = await this.questionnairemodel.findById(id);
  38. if (name) {
  39. questionnaire.name = name;
  40. }
  41. if (num) {
  42. questionnaire.num = num;
  43. }
  44. if (type) {
  45. questionnaire.type = type;
  46. }
  47. if (question) {
  48. questionnaire.question = question;
  49. }
  50. return await questionnaire.save();
  51. }
  52. // 查询
  53. async query({ skip, limit, ...num }) {
  54. const total = await this.questionnairemodel.count(num);
  55. const data = await this.questionnairemodel
  56. .find(num)
  57. .skip(Number(skip))
  58. .limit(Number(limit));
  59. const result = { total, data };
  60. return result;
  61. }
  62. // 查询详情
  63. async show({ id }) {
  64. const questionnaire = await this.questionnairemodel.findById(id);
  65. return questionnaire;
  66. }
  67. /**
  68. * 导出问卷
  69. * @param {Object} { range, direction, questionnaireid, modelList } 数据集合
  70. * @property Object range 学生的查询范围
  71. * @property String direction horizontal横向/vertical纵向
  72. * @property String questionnaireid 问卷id
  73. * @property Array modelList 要导出的字段,学生和问卷都在这里, 学生的table:student, 问卷的table:questionnaire
  74. */
  75. async export({ range, direction, questionnaireid, modelList }) {
  76. // 将期批班转换
  77. modelList = modelList.map(i => {
  78. const { model } = i;
  79. if (model === 'termid') i.model = 'termname';
  80. else if (model === 'batchid') i.model = 'batchname';
  81. else if (model === 'classid') i.model = 'classname';
  82. return i;
  83. });
  84. // 获取问卷
  85. let questionnaire = await this.questionnairemodel.findById(questionnaireid);
  86. if (!questionnaire) { throw new BusinessError(ErrorCode.DATA_NOT_EXIST, '未找到问卷信息'); }
  87. questionnaire = JSON.parse(JSON.stringify(questionnaire));
  88. // 修改条件,termid变成数组了,需要一个一个查出来
  89. const { planid, termid, batchid, classid } = range;
  90. const queryObject = {};
  91. if (planid) queryObject.planid = planid;
  92. if (batchid) queryObject.batchid = batchid;
  93. if (classid) queryObject.classid = classid;
  94. const studentList = [];
  95. const questAnswerList = [];
  96. for (const t of termid) {
  97. queryObject.termid = t;
  98. const obj = await this.toGetData(queryObject, questionnaireid);
  99. if (!obj) continue;
  100. const { studentList: stuList, questAnswerList: qaList } = obj;
  101. if (stuList) studentList.push(...stuList);
  102. if (qaList) questAnswerList.push(...qaList);
  103. }
  104. // fn,根据范围+问卷 得出文件名
  105. const fn = await this.toSetFileName(questionnaire.name, range);
  106. let excelData;
  107. if (direction === 'horizontal') {
  108. excelData = this.horizontalSetData(studentList, questAnswerList, modelList);
  109. } else if (direction === 'vertical') {
  110. excelData = this.verticaSetData(studentList, questAnswerList, modelList);
  111. } else {
  112. throw new BusinessError(ErrorCode.DATA_NOT_EXIST, '未找到excel的表头排列方式');
  113. }
  114. if (excelData) {
  115. const { head, data } = excelData;
  116. return await this.ctx.service.util.toExcel(data, head, fn);
  117. }
  118. }
  119. /**
  120. * 获取学生与学生回答的问卷
  121. * @param {Object} condition 查询学生和学生回答问卷的条件
  122. * @param {String} questionnaireid 问卷id
  123. */
  124. async toGetData(condition, questionnaireid) {
  125. // 获取学生
  126. let { data: studentList } = await this.ctx.service.student.query(condition);
  127. if (studentList.length <= 0) { throw new BusinessError(ErrorCode.DATA_NOT_EXIST, '未找到任何学生信息'); }
  128. studentList = JSON.parse(JSON.stringify(studentList));
  129. // 再获取问卷答案
  130. let questAnswerList = await this.ctx.model.Uploadquestion.find({
  131. ...condition,
  132. questionnaireid,
  133. });
  134. if (!questAnswerList) { throw new BusinessError(ErrorCode.DATA_NOT_EXIST, '未找到任何完成的问卷'); }
  135. questAnswerList = JSON.parse(JSON.stringify(questAnswerList));
  136. const obj = {};
  137. if (studentList) obj.studentList = studentList;
  138. if (questAnswerList) obj.questAnswerList = questAnswerList;
  139. return obj;
  140. }
  141. /**
  142. * 获得导出文件的文件名
  143. * @param {String} questName 问卷名
  144. * @param {Object} range 范围(计划-期-批-班)
  145. * @return {String} fn 文件名
  146. */
  147. async toSetFileName(questName, range) {
  148. let fn = `-${questName}`;
  149. const { planid, termid, batchid, classid } = range;
  150. if (classid) {
  151. const cla = await this.ctx.service.class.fetch({ id: classid });
  152. if (cla) {
  153. const { name, term } = cla;
  154. if (name) fn = `${name.includes('班') ? name : `${name}班`}${fn}`;
  155. if (term) fn = `第${term}期${fn}`;
  156. }
  157. } else if (batchid) {
  158. const tid = _.head(termid);
  159. const obj = await this.toGetFn(tid, batchid);
  160. if (obj) {
  161. const { term, batch } = obj;
  162. if (batch) fn = `第${batch}批${fn}`;
  163. if (term) fn = `第${term}期${fn}`;
  164. }
  165. } else if (termid) {
  166. if (termid.length === 1) {
  167. const obj = await this.toGetFn(_.head(termid));
  168. if (obj) {
  169. const { term } = obj;
  170. if (term) fn = `第${term}期${fn}`;
  171. }
  172. } else {
  173. let tStr = '';
  174. for (let i = 0; i < termid.length; i++) {
  175. const tid = termid[i];
  176. const obj = await this.toGetFn(tid);
  177. if (obj) {
  178. const { term } = obj;
  179. if (term) {
  180. if (i === 0) { tStr += `${term}期`; } else { tStr += `,${term}期`; }
  181. }
  182. }
  183. }
  184. fn = `${tStr}${fn}`;
  185. }
  186. } else {
  187. const trainPlan = await this.ctx.model.Trainplan.findById(planid);
  188. if (trainPlan) {
  189. const { title } = trainPlan;
  190. if (title) fn = `${title}${fn}`;
  191. }
  192. }
  193. return fn;
  194. }
  195. /**
  196. * 获取文件的期/期批
  197. * @param {String} termid 期id
  198. * @param {String} batchid 批id
  199. */
  200. async toGetFn(termid, batchid) {
  201. const trainPlan = await this.ctx.model.Trainplan.findOne({ 'termnum._id': ObjectId(termid) });
  202. if (!trainPlan) throw new BusinessError(ErrorCode.DATA_NOT_EXIST, '未找到年度计划信息');
  203. const { termnum } = trainPlan;
  204. if (!termnum) throw new BusinessError(ErrorCode.DATA_NOT_EXIST, '未找到年度计划的期信息');
  205. const obj = {};
  206. if (termid) {
  207. const term = termnum.id(termid);
  208. if (term) obj.term = term.term;
  209. if (batchid) {
  210. const { batchnum } = term;
  211. const batch = batchnum.id(batchid);
  212. if (batch) obj.batch = batch.batch;
  213. }
  214. }
  215. return obj;
  216. }
  217. /**
  218. * 横向组织数据
  219. * @param {Array} studentList 学生列表
  220. * @param {Array} questAnswerList 学生回答问卷列表
  221. * @param {Array} modelList 需要导出的字段
  222. */
  223. horizontalSetData(studentList, questAnswerList, modelList) {
  224. // 第一步,组织excel表头
  225. const head = modelList.map(i => {
  226. const { zh, model, _id } = i;
  227. const headObj = { header: zh };
  228. if (model) headObj.key = model;
  229. else if (_id) headObj.key = _id;
  230. headObj.width = 20;
  231. return headObj;
  232. });
  233. // 第二步,组织数据需要按照modelList的顺序进行整理
  234. const data = [];
  235. for (const stu of studentList) {
  236. const obj = {};
  237. for (const m of modelList) {
  238. const { model, table, _id } = m;
  239. if (table === 'student') {
  240. const prop = _.get(stu, model, '');
  241. obj[model] = prop;
  242. } else if (table === 'questionnaire') {
  243. const qar = questAnswerList.find(f => f.studentid === stu._id);
  244. // 没找到该学生的问卷,下一个
  245. if (!qar) continue;
  246. const { answers } = qar;
  247. // 回答的数据不存在/不是数组,数据格式不对,下一个
  248. if (!(answers && _.isArray(answers))) continue;
  249. const ar = answers.find(f => f.questionid === _id);
  250. // 没找到答案,下个
  251. if (!ar) continue;
  252. const { answer } = ar;
  253. // 没有答案,下个
  254. if (!answer) continue;
  255. obj[_id] = _.trim(answer, '"');
  256. }
  257. }
  258. data.push(obj);
  259. }
  260. const obj = {};
  261. if (head)obj.head = head;
  262. if (data) obj.data = data;
  263. return obj;
  264. }
  265. /**
  266. * 纵向组织数据
  267. * @param {Array} studentList 学生列表
  268. * @param {Array} questAnswerList 学生回答问卷列表
  269. * @param {Array} modelList 需要导出的字段
  270. */
  271. verticaSetData(studentList, questAnswerList, modelList) {
  272. // 第一步,组织excel表头,纵向不需要第一行表头,开始就是数据,表头是第一列
  273. const head = [{ key: 'c', width: 20 }];
  274. for (let i = 1; i <= studentList.length; i++) {
  275. head.push({ key: `c${i}`, width: 20 });
  276. }
  277. // 第二部,整理数据
  278. const data = [];
  279. for (const m of modelList) {
  280. const { table, model, _id, zh } = m;
  281. const obj = { c: zh };
  282. if (table === 'student') {
  283. for (let i = 0; i < studentList.length; i++) {
  284. const stu = studentList[i];
  285. const value = _.get(stu, model, '');
  286. if (value) obj[`c${i + 1}`] = value;
  287. }
  288. } else if (table === 'questionnaire') {
  289. for (let i = 0; i < studentList.length; i++) {
  290. const stu = studentList[i];
  291. const qar = questAnswerList.find(f => f.studentid === stu._id);
  292. // 没找到该学生的问卷,下一个
  293. if (!qar) continue;
  294. const { answers } = qar;
  295. // 回答的数据不存在/不是数组,数据格式不对,下一个
  296. if (!(answers && _.isArray(answers))) continue;
  297. const ar = answers.find(f => f.questionid === _id);
  298. // 没找到答案,下个
  299. if (!ar) continue;
  300. const { answer } = ar;
  301. // 没有答案,下个
  302. if (!answer) continue;
  303. obj[`c${i + 1}`] = _.trim(answer, '"');
  304. }
  305. }
  306. data.push(obj);
  307. }
  308. const obj = {};
  309. if (head) obj.head = head;
  310. if (data) obj.data = data;
  311. return obj;
  312. }
  313. }
  314. module.exports = QuestionnaireService;