123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702 |
- 'use strict';
- const assert = require('assert');
- const _ = require('lodash');
- const { ObjectId } = require('mongoose').Types;
- const { CrudService } = require('naf-framework-mongoose/lib/service');
- const { BusinessError, ErrorCode } = require('naf-core').Error;
- const moment = require('moment');
- class StudentService extends CrudService {
- constructor(ctx) {
- super(ctx, 'student');
- this.model = this.ctx.model.Student;
- this.umodel = this.ctx.model.User;
- this.tmodel = this.ctx.model.Trainplan;
- this.clamodel = this.ctx.model.Class;
- this.upmodel = this.ctx.model.Uploadtask;
- this.gmodel = this.ctx.model.Group;
- this.psmodel = this.ctx.model.Personalscore;
- this.gsmodel = this.ctx.model.Groupscore;
- this.uqmodel = this.ctx.model.Uploadquestion;
- this.scoremodel = this.ctx.model.Score;
- this.leavemodel = this.ctx.model.Leave;
- this.attendmodel = this.ctx.model.Attendance;
- const { baseUrl } = _.get(this.ctx.app.config, 'mission');
- if (baseUrl) this.missionBase = baseUrl;
- }
- async create(data) {
- const { name, phone: mobile, gender, id_number } = data;
- const count = await this.model.count({ id_number });
- if (count) throw new BusinessError(ErrorCode.DATA_EXISTED, '该身份证号已存在,若未发现错误,请联系开发人员查证');
- const res = await this.model.create(data);
- if (res) {
- const obj = {
- name,
- mobile,
- gender,
- type: '4',
- passwd: '12345678',
- uid: res._id,
- };
- const user = await this.ctx.service.user.create(obj);
- }
- return res;
- }
- async delete({ id }) {
- // 删除小组中的这个人,作业表,问卷表,评分,考勤,用户表,学生表
- await this.gmodel.update({ 'students.stuid': id }, { $pull: { students: { stuid: id } } });
- await this.upmodel.deleteMany({ studentid: id });
- await this.uqmodel.deleteMany({ studentid: id });
- await this.scoremodel.deleteMany({ stuid: id });
- await this.attendmodel.deleteMany({ studentid: id });
- await this.umodel.deleteOne({ uid: id });
- await this.model.deleteOne({ _id: ObjectId(id) });
- }
- async update({ id }, data) {
- const student = await this.model.findByIdAndUpdate(id, data);
- if (student) {
- const { phone, name } = data;
- if (phone && name) {
- await this.umodel.findOneAndUpdate({ uid: id }, { mobile: phone, name });
- }
- }
- return student;
- }
- // 查询
- async query({ name, ...info } = {}, { skip = 0, limit = 0 } = {}) {
- const query = { ...info };
- if (name) {
- query.name = { $regex: name };
- }
- let data = await this.model.find(query).skip(parseInt(skip)).limit(parseInt(limit));
- const planids = _.uniq(data.map((i) => ObjectId(i.planid)));
- const plan = await this.ctx.model.Trainplan.find({ _id: planids });
- data = JSON.parse(JSON.stringify(data));
- for (const i of data) {
- if (i.classid) {
- const d = await this.clamodel.findById(i.classid).exec();
- if (d) i.classid = JSON.parse(JSON.stringify(d));
- }
- }
- data = data.map((i) => {
- if (i.classid && _.isObject(i.classid)) {
- i.classname = _.get(i.classid, 'name');
- i.classid = _.get(i.classid, '_id');
- }
- const p = plan.find((f) => ObjectId(f._id).equals(i.planid));
- if (p) {
- const { termnum, title } = p;
- i.plantitle = title;
- const tr = termnum.id(i.termid);
- if (tr) {
- i.termname = _.get(tr, 'term');
- const br = tr.batchnum.id(i.batchid);
- if (br) {
- i.batchname = _.get(br, 'batch');
- const startDate = _.get(br, 'startdate');
- if (startDate) {
- i.insurance = moment(startDate).add(1, 'd').format('YYYY-MM-DD');
- }
- }
- }
- }
- return i;
- });
- return data;
- }
- async count({ name, ...info } = {}) {
- const query = { ...info };
- if (name) {
- query.name = { $regex: name };
- }
- return await this.model.count(query);
- }
- // 查询
- async seek({ termid, type, batchid, skip, limit }) {
- const total = await this.model.count({
- termid,
- type,
- batchid,
- $or: [{ classid: null }, { classid: '' }],
- });
- const data = await this.model
- .find({
- termid,
- type,
- batchid,
- $or: [{ classid: null }, { classid: '' }],
- })
- .skip(Number(skip))
- .limit(Number(limit));
- const result = { total, data };
- return result;
- }
- /**2024-07-25根据寝室和班级找学生信息,用不上了 */
- async findbedroom(data) {
- const { batchid, classid } = data;
- const result = [];
- // 如果传的是批次id
- if (batchid) {
- // 查询该批次下的所有学生
- const students = await this.model.find({ batchid });
- const bedroomList = new Set();
- // 查询该批次的所有寝室号
- for (const student of students) {
- bedroomList.add(student.bedroom);
- }
- let studentList = [];
- // 查询该批次所有寝室下的学生名单
- for (const bedroom of bedroomList) {
- const newstudents = await this.model.find({ bedroom });
- for (const newstudent of newstudents) {
- studentList.push(newstudent.name);
- }
- result.push({ bedroom, studentList });
- studentList = [];
- }
- }
- // 如果传的是班级id
- if (classid) {
- // 查询该班级所有学生
- const students = await this.model.find({ classid });
- const bedroomList = new Set();
- // 查询该班级所有寝室号
- for (const student of students) {
- bedroomList.add(student.bedroom);
- }
- let studentList = [];
- // 查询该班级所有寝室的学生名单
- for (const bedroom of bedroomList) {
- const newstudents = await this.model.find({ bedroom });
- for (const newstudent of newstudents) {
- // 如果寝室中有非本班级学生(混寝),则过滤掉不予显示
- if (newstudent.classid === classid) {
- studentList.push(newstudent.name);
- }
- }
- result.push({ bedroom, studentList });
- studentList = [];
- }
- }
- return result;
- }
- async upjob(data) {
- const { stuid, job } = data;
- const student = await this.model.findById(stuid);
- student.job = job;
- if (job === '班长' || job === '学委') {
- const user = await this.umodel.findOne({ uid: stuid, type: '4' });
- const date = await this.ctx.service.util.updatedate();
- const openid = user.openid;
- const detail = '你已被班主任设置为' + job + ',请及时登录查看';
- const remark = '感谢您的使用';
- if (openid) {
- this.ctx.service.weixin.sendTemplateMsg(this.ctx.app.config.REVIEW_TEMPLATE_ID, openid, '您有一个新的通知', detail, date, remark);
- }
- }
- return await student.save();
- }
- // 根据学生id删除班级
- async deleteclass(data) {
- for (const el of data) {
- const student = await this.model.findById(el);
- if (student) {
- student.classid = '';
- await student.save();
- }
- }
- }
- // 根据班级id查出班级各个学生的分数
- async findscore({ skip, limit, ...info }) {
- const { classid } = info;
- const total = await this.model.count(info);
- const students = await this.model.find(info).skip(Number(skip)).limit(Number(limit));
- const data = [];
- const groups = await this.gmodel.find({ classid });
- for (const student of students) {
- const _student = JSON.parse(JSON.stringify(student));
- const group = groups.find((item) => item.students.find((stuinfo) => stuinfo.stuid === _student.id));
- if (group) {
- _student.groupscore = group.score;
- }
- const tasks = await this.upmodel.find({ studentid: _student.id });
- _student.tasks = tasks;
- data.push(_student);
- }
- return { total, data };
- }
- async findbystuids({ data }) {
- const res = [];
- for (const stuid of data) {
- const stu = await this.model.findById(stuid);
- if (stu) res.push(stu);
- }
- return res;
- }
- // 根据学生id删除学生
- async deletestus(data) {
- // throw new BusinessError(
- // ErrorCode.BUSINESS,
- // '此功能暂不开放,待确定好会出现的以外情况后,再次开放'
- // );
- for (const id of data) {
- await this.umodel.deleteOne({ uid: id });
- await this.upmodel.deleteMany({ studentid: id });
- await this.uqmodel.deleteMany({ studentid: id });
- await this.scoremodel.deleteMany({ stuid: id });
- await this.attendmodel.deleteMany({ studentid: id });
- await this.model.deleteOne({ _id: ObjectId(id) });
- }
- }
- // 批量更新寝室号 2024-07-25不管寝室了,应该用不上了
- async updatabedroom(data) {
- for (const el of data) {
- const student = await this.model.findById(el.id);
- if (student) {
- student.bedroom = el.bedroom;
- await student.save();
- }
- }
- }
- /**
- * 计算班级的优秀学生
- * 规则:班级干部(学生的job!=='普通学生'),检查是否有评优资格(is_fine_status==='0'可以评优),全部评优;
- * 普通学生取总成绩前10的人评优(非10位并列时,名额该占用就占用;第10名若有并列,就全都要)
- * @param {String} param0 {id=>班级id}
- */
- async getFineStudent({ id: classid }) {
- // 获取班级学生列表
- let studentList = await this.model.find({ classid });
- // 重置评优,干部全优秀
- studentList = studentList.map((i) => {
- if (i.job.includes('普通')) i.is_fine = '0';
- else i.is_fine = '1';
- return i;
- });
- // 初始化后取出不需要算的人,他们就这样,没必要算
- const reverseList = studentList.filter((f) => !(f.is_fine !== '2' && f.job.includes('普通')));
- // 过滤出取消评优资格的学生和干部;干部就是优秀;被取消资格就别凑热闹了
- studentList = studentList.filter((f) => f.is_fine !== '2' && f.job.includes('普通'));
- // 获取平时分
- let dailyScoreList = await this.psmodel.find({ classid });
- // 去重复
- dailyScoreList = this.getDailyScore(dailyScoreList);
- studentList = this.dealScoreList(dailyScoreList, studentList, 'daily');
- // 获取作业分
- let taskScoreList = await this.upmodel.find({ classid });
- taskScoreList = this.getTaskScore(taskScoreList);
- studentList = this.dealScoreList(taskScoreList, studentList, 'task');
- // 获取小组分,小组
- const groupList = await this.gmodel.find({ classid });
- const groupScoreList = await this.gsmodel.find({ classid });
- studentList = this.dealGroupScoreList(groupList, groupScoreList, studentList);
- studentList = studentList.map((i) => {
- i.score = _.round((i.daily || 0) + (i.task || 0) + (i.groupscore || 0), 2);
- return i;
- });
- studentList = studentList.sort((a, b) => (b.score * 1 || 0) - (a.score * 1 || 0));
- // 排名
- // eslint-disable-next-line no-unused-vars
- let num = 0;
- for (const student of studentList) {
- // 先判断是否超过第10位,超过就跳出
- if (num >= 10) break;
- const { score, is_fine, job, name, _id } = student;
- // 最开始初始化过所有人的状态,并且将干部和不能评优的人都过滤出去,所以正常学生应该都是没有评优,如果此处已经是优秀,那么就是和前人同分改的,直接跳过就好
- if (is_fine === '1') continue;
- // 没有分凑什么热闹
- if (!score) continue;
- let plus = 1; // 这轮有多少人,到这了,这个人肯定是要改了,所以默认1
- // 评优
- student.is_fine = '1';
- const rlist = studentList.filter((f) => f.score === score && !ObjectId(f._id).equals(_id));
- // 处理同分的人也都变成is_fine
- for (const stud of rlist) {
- stud.is_fine = '1';
- const sindex = studentList.findIndex((f) => ObjectId(stud._id).equals(f._id));
- if (sindex >= 0) {
- studentList[sindex] = stud;
- plus++;
- }
- }
- // num+plus,算下num
- num = num + plus;
- }
- // 算完的和不用算的合并,提交
- const lastList = [...studentList, ...reverseList];
- for (const student of lastList) {
- // const res = await student.save();
- // const { meta, ...data } = student;
- const r = await this.model.findByIdAndUpdate({ _id: ObjectId(student._id) }, { score: student.score, is_fine: student.is_fine });
- }
- }
- /**
- * 将分数放到学生身上
- * @param {Array} scoreList 班级的分数列表
- * @param {Array} studentList 学生列表
- * @param {String} type 分数类型
- */
- dealScoreList(scoreList, studentList, type) {
- scoreList = JSON.parse(JSON.stringify(scoreList));
- studentList = JSON.parse(JSON.stringify(studentList));
- scoreList = _.groupBy(scoreList, 'studentid');
- const arr = [];
- for (let i of studentList) {
- const slist = scoreList[i._id];
- const obj = {};
- if (slist) {
- const score = slist.reduce((p, n) => p + (n.score * 1 || 0), 0);
- obj[type] = score;
- } else {
- obj[type] = 0;
- }
- i = Object.assign(i, obj);
- arr.push(i);
- }
- return arr;
- }
- /**
- * 将 学生所在 组的 团队平均分 + 到学生身上
- * @param {Array} groupList 班级的小组的列表
- * @param {Array} scoreList 所有小组的分数列表
- * @param {Array} studentList 学生列表
- */
- dealGroupScoreList(groupList, scoreList, studentList) {
- scoreList = _.groupBy(scoreList, 'groupid');
- // 算出每组的平均分,之后加给学生
- groupList = groupList.map((i) => {
- const { students } = i;
- if (students.length > 0) {
- const slist = scoreList[i._id];
- if (slist) {
- i.score = slist.reduce((p, n) => p + (n.score * 1 || 0), 0);
- i.score = _.floor(_.divide(i.score, students.length), 2);
- }
- }
- return i;
- });
- // 每个学生加自己的组的平均分
- studentList = studentList.map((i) => {
- const r = groupList.find((f) => f.students.find((sf) => ObjectId(sf.stuid).equals(i._id)));
- if (r) {
- // i.score = _.round((i.score * 1 || 0) + (r.score * 1 || 0), 2);
- i.groupscore = r.score * 1 || 0;
- } else {
- i.groupscore = 0;
- }
- return i;
- });
- return studentList;
- }
- // 平时分去重处理
- getDailyScore(list) {
- const arr = [];
- const mid1 = _.groupBy(list, 'studentid');
- const keys = Object.keys(mid1);
- for (const key of keys) {
- const mid2 = _.uniqBy(mid1[key], 'subid');
- arr.push(...mid2);
- }
- return arr;
- }
- // 作业分去重处理
- getTaskScore(list) {
- const arr = [];
- const mid1 = _.groupBy(list, 'studentid');
- const keys = Object.keys(mid1);
- for (const key of keys) {
- const mid2 = _.uniqBy(mid1[key], 'lessonid');
- arr.push(...mid2);
- }
- return arr;
- }
- // 将学生排号
- async arrangeNumber({ classid }) {
- const studList = await this.model.find({ classid });
- let number = 1;
- // 查每个学生的编号,如果没有,就给赋上值;有,就给number赋上值,然后继续下一位
- for (const stu of studList) {
- if (!stu.number) {
- if (number * 1 < 10) stu.number = `0${number}`;
- else stu.number = number;
- await stu.save();
- } else {
- number = stu.number * 1;
- }
- number = number * 1 + 1;
- }
- number = 1;
- }
- // 建立导出学生名单的任务
- async toExport(body) {
- const fn = await this.toGetFn(body);
- const data = {
- title: fn,
- params: {
- project: 'center',
- service: 'student',
- method: 'exportStudent',
- 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, '未找到任务项目设置');
- }
- }
- // 导出学生
- async exportStudent(body) {
- const limit = 50;
- const { missionid, model, ...data } = body;
- assert(missionid, '缺少任务信息,无法执行任务');
- try {
- // 整理表头+生成excel
- const head = model.map((i) => i.zh);
- let fn = await this.toGetFn(data, missionid);
- let dp = null;
- const { downloadPath, fn: nfn } = await this.ctx.service.util.toAsyncExcel([head], fn, dp);
- dp = downloadPath;
- fn = nfn;
- // 整理数据(分页循环)+写入excel
- let skip = 0;
- const { planid, termid = [], batchid = [], classid = [], isComming } = data;
- const query = {};
- if (classid.length > 0) query.classid = classid;
- else if (batchid.length > 0) query.batchid = batchid;
- else if (termid.length > 0) query.termid = termid;
- else if (planid) query.planid = planid;
- if (isComming === '0') query.isComming = '0';
- else if (isComming !== '0') query.isComming = { $ne: '0' };
- const total = await this.exportToGetTotal(query);
- const times = Math.ceil(total / limit);
- for (let i = 0; i < times; i++) {
- let data = await this.query(query, { skip, limit });
- if (data.length <= 0) break;
- skip = skip + limit;
- data = await this.exportDealData(data);
- const dataList = data.map((i) => {
- const obj = [];
- for (const m of model) {
- obj.push(i[m.model]);
- }
- return obj;
- });
- await this.ctx.service.util.toAsyncExcel(dataList, fn, dp);
- const per = Math.ceil(((i + 1) / times) * 100);
- this.ctx.service.util.updateProcess(missionid, per);
- }
- this.ctx.service.util.updateProcess(missionid, '100', '2', {
- uri: dp,
- });
- } catch (error) {
- console.log(error);
- this.ctx.service.util.updateProcess(missionid, undefined, '3');
- }
- }
- // 处理数据
- async exportDealData(data) {
- const changeList = [
- {
- model: 'is_fine',
- options: [
- { value: '0', to: '否' },
- { value: '1', to: '是' },
- { value: '2', to: '无资格' },
- ],
- },
- {
- model: 'isComming',
- options: [
- { value: '0', to: '未报到' },
- { value: '1', to: '已报到' },
- ],
- },
- {
- model: 'family_is_hard',
- options: [
- { value: '0', to: '否' },
- { value: '1', to: '是' },
- ],
- },
- {
- model: 'have_grant',
- options: [
- { value: '0', to: '否' },
- { value: '1', to: '是' },
- ],
- },
- {
- model: 'type',
- options: [
- { value: '0', to: '正常' },
- { value: '1', to: '特殊' },
- ],
- },
- {
- model: 'status',
- options: [
- { value: '0', to: '待确定' },
- { value: '1', to: '确定' },
- { value: '2', to: '失败' },
- ],
- },
- {
- model: 'cert',
- options: [
- { value: '0', to: '未打印' },
- { value: '1', to: '已打印' },
- ],
- },
- {
- model: 'schid',
- type: 'get',
- format: (i) => i.school_name,
- },
- {
- model: 'termid',
- type: 'get',
- format: (i) => i.termname,
- },
- {
- model: 'batchid',
- type: 'get',
- format: (i) => i.batchname,
- },
- {
- model: 'classid',
- type: 'get',
- format: (i) => i.classname,
- },
- {
- model: 'bedroomid',
- type: 'get',
- format: (i) => i.bedroom,
- },
- ];
- // 需要请求的数据, 学校数据
- for (const i of data) {
- for (const m of changeList) {
- const { model, options, type, format } = m;
- if (type === 'get') {
- // 请求转换
- i[model] = format(i);
- } else {
- // 字典转换
- const v = _.get(i, model);
- if (!v) {
- // 没有默认第一个
- const option = _.head(options);
- i[model] = option.to;
- } else {
- const option = options.find((f) => f.value === v);
- if (option) i[model] = option.to;
- else {
- // 没有默认第一个
- const option = _.head(options);
- i[model] = option.to;
- }
- }
- }
- }
- }
- return data;
- }
- // 导出,获取总数,分页处理
- async exportToGetTotal(query) {
- const total = await this.model.count(query);
- // const { planid, termid = [], batchid = [], classid = [], isComming } = data;
- // if (classid.length > 0) total = await this.model.count({ classid });
- // else if (batchid.length > 0) total = await this.model.count({ batchid });
- // else if (termid.length > 0) total = await this.model.count({ termid });
- // else if (planid) total = await this.model.count({ planid });
- return total;
- }
- // 导出,获取文件名
- async toGetFn(data, missionid) {
- const { planid, termid = [], batchid = [], classid = [] } = data;
- let fn = '学生名单';
- const fnList = [];
- // 如果有班级id,直接用班级id查,然后拼接导出文件名;第x期-第x批-第x班;第x期-第x批-第x班...学生名单
- if (classid.length > 0) {
- for (const cid of classid) {
- const cla = await this.ctx.service.class.fetch({ id: cid });
- if (cla) fnList.push(`第${cla.term}期-第${cla.batch}批-${cla.name}班`);
- }
- } else {
- // 如果没有年度计划id,那就是全库学生导出
- if (!planid) return fn;
- const trainPlan = await this.ctx.model.Trainplan.findById(planid);
- if (!trainPlan) {
- if (missionid) this.ctx.service.util.updateProcess(missionid, '0', '3');
- throw new BusinessError(ErrorCode.SERVICE_FAULT, '学生导出,查询计划失败');
- }
- if (termid.length > 0) {
- for (const tid of termid) {
- const { termnum = [] } = trainPlan;
- const term = termnum.id(tid);
- if (!term) continue;
- if (batchid.length > 0) {
- // 先看批次: 第x期-第x批;第x期-第x批....学生名单
- const { batchnum = [] } = term;
- for (const bid of batchid) {
- const batch = batchnum.id(bid);
- if (batch) fnList.push(`第${term.term}期-第${batch.batch}批`);
- }
- } else {
- // 只有期:第x期;第x期...学生名单
- fnList.push(`第${term.term}期`);
- }
- }
- } else {
- // 只有计划id: xxx计划学生名单
- fnList.push(trainPlan.title);
- }
- }
- fn = `${fnList.join(';')}${fn}`;
- return fn;
- }
- // 确认学生打印证书
- async printCert({ ids }) {
- const res = await this.model.updateMany({ _id: { $in: ids.map((i) => ObjectId(i)) } }, { cert: '1' });
- return res;
- }
- // 根据计划id找到学校是否上传学生
- async getSchoolStudent({ planid }) {
- const res = await this.model.aggregate([{ $match: { planid } }, { $group: { _id: '$schid', sum: { $sum: 1 } } }, { $project: { _id: 0, schid: '$_id', schnum: '$sum' } }]);
- return res;
- }
- }
- module.exports = StudentService;
|