questionnaire.js 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453
  1. 'use strict';
  2. const assert = require('assert');
  3. const _ = require('lodash');
  4. const moment = require('moment');
  5. const { ObjectId } = require('mongoose').Types;
  6. const { CrudService } = require('naf-framework-mongoose/lib/service');
  7. const { BusinessError, ErrorCode } = require('naf-core').Error;
  8. class QuestionnaireService extends CrudService {
  9. constructor(ctx) {
  10. super(ctx, 'qusetionnaire');
  11. this.questionnairemodel = this.ctx.model.Questionnaire;
  12. this.questionmodel = this.ctx.model.Question;
  13. const { baseUrl } = _.get(this.ctx.app.config, 'mission');
  14. if (baseUrl) this.missionBase = baseUrl;
  15. }
  16. // 插入问卷
  17. async create(data) {
  18. const { name, num, type, question } = data;
  19. assert(name, '问卷名称不能为空');
  20. assert(num, '问卷序号不能为空');
  21. assert(type, '问卷类型不能为空');
  22. const quedata = [];
  23. for (const elm of question) {
  24. const ques = await this.questionmodel.findById(elm);
  25. if (ques) {
  26. quedata.push(ques);
  27. }
  28. }
  29. data.question = quedata;
  30. return await this.questionnairemodel.create(data);
  31. }
  32. // 根据id删除问卷
  33. async delete({ id }) {
  34. await this.questionnairemodel.findByIdAndDelete(id);
  35. return 'deleted';
  36. }
  37. // 根据id更新问卷信息
  38. async update({ id }, data) {
  39. const { name, num, type, question } = data;
  40. const questionnaire = await this.questionnairemodel.findById(id);
  41. if (name) {
  42. questionnaire.name = name;
  43. }
  44. if (num) {
  45. questionnaire.num = num;
  46. }
  47. if (type) {
  48. questionnaire.type = type;
  49. }
  50. if (question) {
  51. const quedata = [];
  52. for (const elm of question) {
  53. if (elm && !_.isObject(elm)) {
  54. const ques = await this.questionmodel.findById(elm);
  55. if (ques) {
  56. quedata.push(ques);
  57. }
  58. } else if (elm) quedata.push(elm);
  59. }
  60. questionnaire.question = quedata;
  61. }
  62. return await questionnaire.save();
  63. }
  64. // 查询
  65. async query({ skip, limit, ...num }) {
  66. const total = await this.questionnairemodel.count(num);
  67. const data = await this.questionnairemodel
  68. .find(num)
  69. .skip(Number(skip))
  70. .limit(Number(limit));
  71. const result = { total, data };
  72. return result;
  73. }
  74. // 查询详情
  75. async show({ id }) {
  76. const questionnaire = await this.questionnairemodel.findById(id);
  77. return questionnaire;
  78. }
  79. // 建立任务
  80. async toExport(body) {
  81. const { range, questionnaireid } = body;
  82. const questionnaire = await this.questionnairemodel.findById(
  83. questionnaireid
  84. );
  85. if (!questionnaire) {
  86. throw new BusinessError(ErrorCode.DATA_NOT_EXIST, '未找到问卷信息');
  87. }
  88. const fn = await this.toSetFileName(questionnaire.name, range);
  89. const data = {
  90. title: fn,
  91. params: {
  92. project: 'center',
  93. service: 'questionnaire',
  94. method: 'export',
  95. body,
  96. },
  97. };
  98. if (this.missionBase) {
  99. const url = `${this.missionBase}/api/mission`;
  100. const res = await this.ctx.curl(url, {
  101. method: 'post',
  102. headers: {
  103. 'content-type': 'application/json',
  104. },
  105. data,
  106. dataType: 'json',
  107. });
  108. if (res.status !== 200 || res.data.errcode !== 0) {
  109. throw new BusinessError(ErrorCode.SERVICE_FAULT, '创建任务失败');
  110. }
  111. } else {
  112. throw new BusinessError(ErrorCode.SERVICE_FAULT, '未找到任务项目设置');
  113. }
  114. }
  115. /**
  116. * 导出问卷
  117. * @param {Object} { range, direction, questionnaireid, modelList, missionid } 数据集合
  118. * @property Object range 学生的查询范围
  119. * @property String direction horizontal横向/vertical纵向
  120. * @property String questionnaireid 问卷id
  121. * @property Array modelList 要导出的字段,学生和问卷都在这里, 学生的table:student, 问卷的table:questionnaire
  122. * @property String missionid 任务id
  123. */
  124. async export({ range, direction, questionnaireid, modelList, missionid }) {
  125. assert(missionid, '缺少任务信息,无法执行任务');
  126. try {
  127. // 将期批班转换
  128. modelList = modelList.map(i => {
  129. const { model } = i;
  130. if (model === 'termid') i.model = 'termname';
  131. else if (model === 'batchid') i.model = 'batchname';
  132. else if (model === 'classid') i.model = 'classname';
  133. return i;
  134. });
  135. // 获取问卷
  136. const questionnaire = await this.questionnairemodel.findById(questionnaireid);
  137. if (!questionnaire) {
  138. throw new BusinessError(ErrorCode.DATA_NOT_EXIST, '未找到问卷信息');
  139. }
  140. let fn = await this.toSetFileName(questionnaire.name, range);
  141. // this.ctx.service.util.updateProcess(missionid, '25');
  142. // 修改条件,termid变成数组了,需要一个一个查出来
  143. const { planid, batchid, classid } = range;
  144. let { termid } = range;
  145. const queryObject = {};
  146. if (planid) queryObject.planid = planid;
  147. if (batchid) queryObject.batchid = batchid;
  148. if (classid) queryObject.classid = classid;
  149. let count = 0;
  150. let dp;
  151. const qkeys = Object.keys(range);
  152. if (qkeys.length === 2 && termid.length <= 0) {
  153. // 说明只有planid
  154. const plan = await this.ctx.model.Trainplan.findOne({ _id: ObjectId(planid) });
  155. if (!plan) throw new BusinessError(ErrorCode.DATA_NOT_EXIST, '未找到指定年度计划');
  156. termid = plan.termnum.map(i => JSON.parse(JSON.stringify(i._id)));
  157. }
  158. for (const t of termid) {
  159. queryObject.termid = t;
  160. let data = await this.toGetData(
  161. queryObject,
  162. questionnaireid,
  163. direction,
  164. modelList
  165. );
  166. if (!data) continue;
  167. if (count !== 0 && direction === 'horizontal') data.shift();
  168. if (count !== 0 && direction === 'vertical') {
  169. data = data.map(i => {
  170. i.shift();
  171. return i;
  172. });
  173. }
  174. const {
  175. downloadPath,
  176. fn: newFileName,
  177. } = await this.ctx.service.util.toAsyncExcel(data, fn, dp, direction);
  178. dp = downloadPath;
  179. fn = newFileName;
  180. count++;
  181. }
  182. this.ctx.service.util.updateProcess(missionid, '100', '2', {
  183. uri: dp,
  184. });
  185. } catch (error) {
  186. console.log(error);
  187. this.ctx.service.util.updateProcess(missionid, undefined, '3');
  188. }
  189. }
  190. /**
  191. * 获取学生与学生回答的问卷
  192. * @param {Object} condition 查询学生和学生回答问卷的条件
  193. * @param {String} questionnaireid 问卷id
  194. * @param {String} direction 方向
  195. * @param {Array} modelList 导出字段
  196. */
  197. async toGetData(condition, questionnaireid, direction, modelList) {
  198. // 获取学生
  199. let studentList = await this.ctx.service.student.query(condition);
  200. if (studentList.length <= 0) {
  201. this.ctx.logger.error(`${JSON.stringify(condition)}条件下,未找到任何信息`);
  202. return;
  203. // throw new BusinessError(ErrorCode.DATA_NOT_EXIST, '未找到任何学生信息');
  204. }
  205. studentList = JSON.parse(JSON.stringify(studentList));
  206. // 再获取问卷答案
  207. const questAnswerList = await this.ctx.model.Uploadquestion.find({
  208. ...condition,
  209. questionnaireid,
  210. });
  211. if (!questAnswerList) {
  212. this.ctx.logger.error(`${JSON.stringify(condition)}&${questionnaireid}条件下,未找到任何完成的问卷`);
  213. return;
  214. }
  215. let res = [];
  216. let data = [];
  217. if (direction === 'horizontal') {
  218. const head = this.getHead(direction, modelList, studentList.length);
  219. data = this.horizontalSetData(studentList, questAnswerList, modelList);
  220. res.push(head.map(i => i.header));
  221. for (const d of data) {
  222. const mid = [];
  223. for (const h of head) {
  224. const { key } = h;
  225. mid.push(_.get(d, key, ''));
  226. }
  227. res.push(mid);
  228. }
  229. } else if (direction === 'vertical') {
  230. data = this.verticaSetData(studentList, questAnswerList, modelList);
  231. res = data.map(i => Object.values(i));
  232. } else {
  233. throw new BusinessError(
  234. ErrorCode.DATA_NOT_EXIST,
  235. '未找到excel的表头排列方式'
  236. );
  237. }
  238. return res;
  239. }
  240. /**
  241. * 获得导出文件的文件名
  242. * @param {String} questName 问卷名
  243. * @param {Object} range 范围(计划-期-批-班)
  244. * @return {String} fn 文件名
  245. */
  246. async toSetFileName(questName, range) {
  247. let fn = `-${questName}`;
  248. const { planid, termid, batchid, classid } = range;
  249. if (classid) {
  250. const cla = await this.ctx.service.class.fetch({ id: classid });
  251. if (cla) {
  252. const { name, term } = cla;
  253. if (name) fn = `${name.includes('班') ? name : `${name}班`}${fn}`;
  254. if (term) fn = `第${term}期${fn}`;
  255. }
  256. } else if (batchid) {
  257. const tid = _.head(termid);
  258. const obj = await this.toGetFn(tid, batchid);
  259. if (obj) {
  260. const { term, batch } = obj;
  261. if (batch) fn = `第${batch}批${fn}`;
  262. if (term) fn = `第${term}期${fn}`;
  263. }
  264. } else if (termid) {
  265. if (termid.length === 1) {
  266. const obj = await this.toGetFn(_.head(termid));
  267. if (obj) {
  268. const { term } = obj;
  269. if (term) fn = `第${term}期${fn}`;
  270. }
  271. } else {
  272. let tStr = '';
  273. for (let i = 0; i < termid.length; i++) {
  274. const tid = termid[i];
  275. const obj = await this.toGetFn(tid);
  276. if (obj) {
  277. const { term } = obj;
  278. if (term) {
  279. if (i === 0) {
  280. tStr += `${term}期`;
  281. } else {
  282. tStr += `,${term}期`;
  283. }
  284. }
  285. }
  286. }
  287. fn = `${tStr}${fn}`;
  288. }
  289. } else {
  290. const trainPlan = await this.ctx.model.Trainplan.findById(planid);
  291. if (trainPlan) {
  292. const { title } = trainPlan;
  293. if (title) fn = `${title}${fn}`;
  294. }
  295. }
  296. return fn;
  297. }
  298. /**
  299. * 获取文件的期/期批
  300. * @param {String} termid 期id
  301. * @param {String} batchid 批id
  302. */
  303. async toGetFn(termid, batchid) {
  304. const trainPlan = await this.ctx.model.Trainplan.findOne({
  305. 'termnum._id': ObjectId(termid),
  306. });
  307. if (!trainPlan) {
  308. throw new BusinessError(ErrorCode.DATA_NOT_EXIST, '未找到年度计划信息');
  309. }
  310. const { termnum } = trainPlan;
  311. if (!termnum) {
  312. throw new BusinessError(
  313. ErrorCode.DATA_NOT_EXIST,
  314. '未找到年度计划的期信息'
  315. );
  316. }
  317. const obj = {};
  318. if (termid) {
  319. const term = termnum.id(termid);
  320. if (term) obj.term = term.term;
  321. if (batchid) {
  322. const { batchnum } = term;
  323. const batch = batchnum.id(batchid);
  324. if (batch) obj.batch = batch.batch;
  325. }
  326. }
  327. return obj;
  328. }
  329. /**
  330. * 取出对应方向的表头
  331. * @param {String} type 横纵向类型
  332. * @param {Array} modelList 导出字段
  333. * @param {Number} studentList count的学生数量
  334. */
  335. getHead(type, modelList, studentList) {
  336. let head = [];
  337. if (type === 'horizontal') {
  338. // 横向头
  339. head = modelList.map(i => {
  340. const { zh, model, _id } = i;
  341. const headObj = { header: zh };
  342. if (model) headObj.key = model;
  343. else if (_id) headObj.key = _id;
  344. headObj.width = 20;
  345. return headObj;
  346. });
  347. } else {
  348. // 纵向头
  349. const head = [{ key: 'c', width: 20 }];
  350. for (let i = 1; i <= studentList.length; i++) {
  351. head.push({ key: `c${i}`, width: 20 });
  352. }
  353. }
  354. return head;
  355. }
  356. /**
  357. * 横向组织数据
  358. * @param {Array} studentList 学生列表
  359. * @param {Array} questAnswerList 学生回答问卷列表
  360. * @param {Array} modelList 需要导出的字段
  361. */
  362. horizontalSetData(studentList, questAnswerList, modelList) {
  363. // 组织数据需要按照modelList的顺序进行整理
  364. const data = [];
  365. for (const stu of studentList) {
  366. const obj = {};
  367. for (const m of modelList) {
  368. const { model, table, _id } = m;
  369. if (table === 'student') {
  370. const prop = _.get(stu, model, '');
  371. obj[model] = prop;
  372. } else if (table === 'questionnaire') {
  373. const qar = questAnswerList.find(f => f.studentid === stu._id);
  374. // 没找到该学生的问卷,下一个
  375. if (!qar) continue;
  376. const { answers } = qar;
  377. // 回答的数据不存在/不是数组,数据格式不对,下一个
  378. if (!(answers && _.isArray(answers))) continue;
  379. const ar = answers.find(f => f.questionid === _id);
  380. // 没找到答案,下个
  381. if (!ar) continue;
  382. const { answer } = ar;
  383. // 没有答案,下个
  384. if (!answer) continue;
  385. obj[_id] = _.trim(answer, '"');
  386. }
  387. }
  388. data.push(obj);
  389. }
  390. // const obj = {};
  391. // if (head) obj.head = head;
  392. // if (data) obj.data = data;
  393. return data;
  394. }
  395. /**
  396. * 纵向组织数据
  397. * @param {Array} studentList 学生列表
  398. * @param {Array} questAnswerList 学生回答问卷列表
  399. * @param {Array} modelList 需要导出的字段
  400. */
  401. verticaSetData(studentList, questAnswerList, modelList) {
  402. // 整理数据
  403. const data = [];
  404. for (const m of modelList) {
  405. const { table, model, _id, zh } = m;
  406. const obj = { c: zh };
  407. if (table === 'student') {
  408. for (let i = 0; i < studentList.length; i++) {
  409. const stu = studentList[i];
  410. const value = _.get(stu, model, '');
  411. if (value) obj[`c${i + 1}`] = value;
  412. }
  413. } else if (table === 'questionnaire') {
  414. for (let i = 0; i < studentList.length; i++) {
  415. const stu = studentList[i];
  416. const qar = questAnswerList.find(f => f.studentid === stu._id);
  417. // 没找到该学生的问卷,下一个
  418. if (!qar) continue;
  419. const { answers } = qar;
  420. // 回答的数据不存在/不是数组,数据格式不对,下一个
  421. if (!(answers && _.isArray(answers))) continue;
  422. const ar = answers.find(f => f.questionid === _id);
  423. // 没找到答案,下个
  424. if (!ar) continue;
  425. const { answer } = ar;
  426. // 没有答案,下个
  427. if (!answer) continue;
  428. obj[`c${i + 1}`] = _.trim(answer, '"');
  429. }
  430. }
  431. data.push(obj);
  432. }
  433. return data;
  434. }
  435. }
  436. module.exports = QuestionnaireService;