Browse Source

Merge branch 'master' of http://git.cc-lotus.info/new_train/service-center

lrf 9 months ago
parent
commit
c23377649f
3 changed files with 429 additions and 4 deletions
  1. 6 0
      app/controller/util.js
  2. 1 0
      app/router.js
  3. 422 4
      app/service/util.js

+ 6 - 0
app/controller/util.js

@@ -22,5 +22,11 @@ class UtilController extends Controller {
     const res = await this.service.schoolImport(this.ctx.request.body);
     this.ctx.ok({ msg: 'created', data: res });
   }
+
+  // 学生参培名单导入
+  async stuImport() {
+    const res = await this.service.stuimport(this.ctx.request.body);
+    this.ctx.ok({ msg: 'created', data: res });
+  }
 }
 module.exports = UtilController;

+ 1 - 0
app/router.js

@@ -365,4 +365,5 @@ module.exports = app => {
   // 学校计划人数导入导出
   router.post('/api/train/schoolDownload', controller.util.schoolDownload); // 导出
   router.post('/api/train/schoolImport', controller.util.schoolImport); // 导入
+  router.post('/api/train/stuImport', controller.util.stuImport); // 学生参培名单导入
 };

+ 422 - 4
app/service/util.js

@@ -201,7 +201,7 @@ XQIDAQAB
     if (sort && _.isString(sort)) {
       sort = { [sort]: desc ? -1 : 1 };
     } else if (sort && _.isArray(sort)) {
-      sort = sort.map((f) => ({ [f]: desc ? -1 : 1 })).reduce((p, c) => ({ ...p, ...c }), {});
+      sort = sort.map(f => ({ [f]: desc ? -1 : 1 })).reduce((p, c) => ({ ...p, ...c }), {});
     }
     return { skip, limit, sort };
   }
@@ -405,7 +405,7 @@ XQIDAQAB
       const c = [];
       if (title) {
         const tit = new Paragraph({
-          children: [new TextRun({ text: title, bold: true })],
+          children: [ new TextRun({ text: title, bold: true }) ],
           heading: HeadingLevel.TITLE,
           alignment: AlignmentType.CENTER,
         });
@@ -413,7 +413,7 @@ XQIDAQAB
       }
       if (author) {
         const auth = new Paragraph({
-          children: [new TextRun({ color: '#000000', text: author })],
+          children: [ new TextRun({ color: '#000000', text: author }) ],
           heading: HeadingLevel.HEADING_2,
           alignment: AlignmentType.RIGHT,
         });
@@ -422,7 +422,7 @@ XQIDAQAB
       if (content && _.isArray(content) && content.length > 0) {
         for (const cont of content) {
           const p = new Paragraph({
-            children: [new TextRun({ text: cont, bold: true })],
+            children: [ new TextRun({ text: cont, bold: true }) ],
           });
           c.push(p);
         }
@@ -638,6 +638,424 @@ XQIDAQAB
       return true;
     }
   }
+  // 学生参培名单导入
+  async stuimport(data) {
+    const { filepath, planid, termid, batchid } = data;
+    assert(filepath, '文件不能为空');
+    assert(planid, '计划不能为空');
+    assert(termid, '期不能为空');
+    assert(batchid, '批次不能为空');
+    // 根据termid取得计划信息
+    const plan = await this.ctx.model.Trainplan.findOne({ 'termnum._id': ObjectId(termid) });
+    if (!plan) {
+      throw new BusinessError(ErrorCode.DATA_NOT_EXIST, '计划信息不存在');
+    }
+    const term = plan.termnum.id(termid);
+    let type = '0';
+    if (term.batchnum && term.batchnum.length > 0 && term.batchnum[0].class && term.batchnum[0].class.length > 0) type = term.batchnum[0].class[0].type;
+    const planyearid = plan.planyearid;
+    // 检查这个范围的学生是否存在,存在的话是否更改过(classid,bedroomid这两项存不存在可以放过,但凡有一个人,就不行了)
+    let dbStuList = await this.ctx.model.Student.find({ planid, termid, batchid });
+    if (dbStuList.length > 0) {
+      // 查这个学校的这期学生是否修改过班级 或 寝室
+      const is_change = dbStuList.find(f => f.classid || f.bedroomid);
+      if (is_change) {
+        throw new BusinessError(
+          ErrorCode.BUSINESS,
+          '该计划该期该批次有学生已经安排班级或寝室 无法进行导入,请对该批次学生进行修改重新导入!'
+        );
+      }
+    }
+    // 2021-06-07 如果学生已经绑定,那也不允许修改名单了
+    const countOpenid = await this.ctx.model.Student.count({ planid, termid, batchid, openid: { $exists: true } });
+    if (countOpenid > 0) throw new BusinessError(ErrorCode.BUSINESS, '已有学生绑定账号,名单无法修改!');
+    let domain = 'http://127.0.0.1';
+    if (process.env.NODE_ENV === 'development') domain = 'http://jytz.jilinjobs.cn';
+    const fullUrl = domain + filepath; // this.ctx.app.config.baseUrl http://127.0.0.1 http://jytz.jilinjobs.cn
+    let studentList = await this.getDataFromExcel(fullUrl);
+    const checkRes = await this.checkData(studentList);
+    const { errorcode } = checkRes;
+    if (errorcode === '1') {
+      return checkRes;
+    }
+    // 2021-05-26 添加与数据库的对比,如果数据库里已经有这个身份证号,就需要提示
+    const countStudent = await this.countStudent(studentList, planid);
+    const { errorcode: csec } = countStudent;
+    if (csec === '1') {
+      return countStudent;
+    }
+    // 整理数据
+    studentList = await this.lastSetData(studentList, {
+      planyearid,
+      planid,
+      batchid,
+      termid,
+      type,
+    });
+    // 复制,删除,添加
+    if (dbStuList.length > 0) {
+      dbStuList = JSON.parse(JSON.stringify(dbStuList));
+      dbStuList = dbStuList.map(i => {
+        delete i.meta;
+        i.studentid = _.clone(i._id);
+        delete i.id;
+        delete i._id;
+        return i;
+      });
+      await this.ctx.model.Student.deleteMany({ planid, termid, batchid });
+      await this.ctx.model.Dstudent.insertMany(dbStuList);
+    }
+    await this.ctx.model.Student.insertMany(studentList);
+    return 'ok';
+  }
+  // 整理excel数据
+  async getDataFromExcel(url) {
+    // 请求文件
+    const file = await this.ctx.curl(`${url}`);
+    if (!(file && file.data)) {
+      throw new BusinessError(ErrorCode.DATA_NOT_EXIST, '未找到上传的名单');
+    }
+    const workbook = new Excel.Workbook();
+    // 读取文件
+    await workbook.xlsx.load(file.data);
+    const worksheet = workbook.getWorksheet(1);
+    if (!worksheet) {
+      throw new BusinessError(ErrorCode.DATA_NOT_EXIST, '未发现excel中有工作表');
+    }
+    // 获取表头,通过方法的返回值,将写死的表头数组返回 回来
+    const cols = this.getStucolumn();
+    // 第一行(表头)
+    const headRow = worksheet.getRow(1);
+    // 设置,检查表头
+    headRow.eachCell((cell, coli) => {
+      console.log(cell.value);
+      if (cell.value !== '序号') {
+        const r = cols.find(f => f.key === cell.value);
+        if (r) {
+          const ri = cols.findIndex(f => f.key === cell.value);
+          // 表头符合要求,做上标记
+          r.colIndex = coli;
+          cols[ri] = r;
+        } else {
+          throw new BusinessError(`模板中"${cell.value}"列错误,请检查excel!`);
+        }
+      }
+    });
+    // 检查表头结果,如果有没有 colIndex,说明表头里有不符合要求的,退回去
+    const excelIsRigth = cols.every(f => f.colIndex);
+    if (!excelIsRigth) throw new BusinessError(ErrorCode.DATA_INVALID, 'Excel表格格式不正确,请使用系统提供的模板,或重新下载模板!');
+    // 删除掉第一行 表头行,这不是数据
+    worksheet.spliceRows(0, 1);
+    const stuList = [];
+    const noWhite = str => str.replace(/\s*/g, '');
+    // 整理数据,根据检查合格的表头行,获取每个格子的数据,制成[object]格式
+    worksheet.eachRow(row => {
+      const stu = {};
+      for (let i = 0; i < cols.length; i++) {
+        const col = cols[i];
+        if (!col) break;
+        let val = _.trim(row.getCell(col.colIndex));
+        if (col.column === 'id_number') val = val.toUpperCase();
+        if (val && val !== '') val = noWhite(val);
+        stu[col.column] = val;
+      }
+      stuList.push(stu);
+    });
+    return stuList;
+  }
+  // 数据校验
+  async checkData(stuList) {
+    const cols = this.getStucolumn();
+    let errorcode = '0';
+    const errormsg = [];
+    for (const stu of stuList) {
+      const { name } = stu;
+      let error = false;
+      let msg = '';
+      // 各个字段检查,最低为非空检查
+      for (const col of cols) {
+        const { key, column } = col;
+        if (!column) throw new BusinessError(ErrorCode.SERVICE_FAULT, '未找到导出的字段名');
+        const val = _.get(stu, column);
+        // 空校验
+        if (!val || val === '') {
+          error = true;
+          msg = `${msg}"${key}"不能为空;`;
+          continue;
+        }
+        // 性别校验
+        if (column === 'gender') {
+          if (!(val.includes('男') || val.includes('女'))) {
+            error = true;
+            msg = `${msg}性别错误;`;
+          }
+          continue;
+        }
+        // 学校名称
+        if (column === 'school_name') {
+          const school = await this.sModel.findOne({ name: { $regex: val } });
+          if (!school) {
+            error = true;
+            msg = `${msg}系统中无此学校;`;
+          }
+          continue;
+        }
+        // 身份证号校验
+        if (column === 'id_number') {
+          const { pass, msg: idmsg } = this.ctx.service.school.idCodeValid(val);
+          if (!pass) {
+            error = true;
+            msg = `${msg}${idmsg};`;
+          }
+          const have_same = stuList.filter(f => f.id_number === val && f.name !== name);
+          if (have_same.length > 0) {
+            error = true;
+            const h = _.head(have_same);
+            const num = have_same.length;
+            if (num === 1) {
+              msg = `${msg}身份证号与本次名单的"${h.name}"重复;`;
+            } else msg = `${msg}身份证号与本次名单中"${h.name}"等${num}人重复;`;
+          }
+          continue;
+        }
+        // 手机号校验
+        if (column === 'phone') {
+          if (!/^\d{11}$/i.test(val)) {
+            error = true;
+            msg = `${msg}手机号位数不正确;`;
+          }
+          const have_same = stuList.filter(f => f.phone === val && f.name !== name);
+          if (have_same.length > 0) {
+            error = true;
+            const h = _.head(have_same);
+            const num = have_same.length;
+            if (num === 1) {
+              msg = `${msg}手机号与本次名单的"${h.name}"重复;`;
+            } else msg = `${msg}手机号与本次名单中"${h.name}"等${num}人重复;`;
+          }
+          continue;
+        }
+        // 专业校验
+        if (column === 'major') {
+          if (val.includes('专业')) {
+            error = true;
+            msg = `${msg}专业列不能含有"专业"二字;`;
+          }
+          continue;
+        }
+        // 入学年份
+        if (column === 'entry_year') {
+          const m = /^\w{4}$/;
+          if (!val.match(m)) {
+            error = true;
+            msg = `${msg}入学年份格式不正确,只填写4位数字;`;
+          }
+          continue;
+        }
+        // 毕业年份
+        if (column === 'finish_year') {
+          const m = /^\w{4}$/;
+          if (!val.match(m)) {
+            error = true;
+            msg = `${msg}毕业年份格式不正确,只填写4位数字;`;
+          }
+          continue;
+        }
+        // 双困检查
+        if (column === 'family_is_hard') {
+          if (!(val.includes('是') || val.includes('否'))) {
+            error = true;
+            msg = `${msg}家庭是否困难填写"是"或"否";`;
+          }
+          continue;
+        }
+        if (column === 'have_grant') {
+          if (!(val.includes('是') || val.includes('否'))) {
+            error = true;
+            msg = `${msg}是否获得过助学金填写"是"或"否";`;
+          }
+          continue;
+        }
+      }
+      if (error) {
+        errorcode = '1';
+        stu.msg = msg;
+        errormsg.push(stu);
+      }
+    }
+    return { errorcode, errormsg };
+  }
+  // excel中学生字段
+  getStucolumn() {
+    const arr = [
+      { key: '姓名', column: 'name' },
+      { key: '性别', column: 'gender' },
+      { key: '民族', column: 'nation' },
+      { key: '身份证号', column: 'id_number' },
+      { key: '学校名称', column: 'school_name' },
+      { key: '学历层次', column: 'edua_level' },
+      { key: '学制', column: 'edua_system' },
+      { key: '院(系)', column: 'faculty' },
+      { key: '专业', column: 'major' },
+      { key: '入学年份', column: 'entry_year' },
+      { key: '毕业年份', column: 'finish_year' },
+      { key: '在校曾担任何种职务', column: 'school_job' },
+      { key: '手机号', column: 'phone' },
+      { key: 'QQ号', column: 'qq' },
+      { key: '家庭所在地', column: 'family_place' },
+      {
+        key: '家庭是否困难',
+        column: 'family_is_hard',
+        change: [
+          { key: '否', value: '0' },
+          { key: '是', value: '1' },
+        ],
+      },
+      {
+        key: '是否获得过助学金',
+        column: 'have_grant',
+        change: [
+          { key: '否', value: '0' },
+          { key: '是', value: '1' },
+        ],
+      },
+    ];
+    return arr;
+  }
+  // 最后整合数据
+  async lastSetData(stuList, data) {
+    const cols = this.getStucolumn();
+    const needChange = cols.filter(f => f.change);
+    const studentList = [];
+    for (const i of stuList) {
+      const d = { ...i, ...data };
+      for (const col of needChange) {
+        const { column, change } = col;
+        if (!column && change && _.isArray(change)) continue;
+        const val = _.get(d, column);
+        if (!val) continue;
+        const r = change.find(f => f.key === val);
+        if (!r) continue;
+        const { value } = r;
+        d[column] = value;
+      }
+      if (d.school_name) {
+        const school = await this.sModel.findOne({ name: { $regex: d.school_name } }).lean();
+        if (school) d.schid = school.code || '';
+      }
+      studentList.push(d);
+    }
+    return studentList;
+  }
+  // 身份证验证
+  idCodeValid(code) {
+    // 身份证号合法性验证
+    // 支持15位和18位身份证号
+    // 支持地址编码、出生日期、校验位验证
+    const city = {
+      11: '北京',
+      12: '天津',
+      13: '河北',
+      14: '山西',
+      15: '内蒙古',
+      21: '辽宁',
+      22: '吉林',
+      23: '黑龙江 ',
+      31: '上海',
+      32: '江苏',
+      33: '浙江',
+      34: '安徽',
+      35: '福建',
+      36: '江西',
+      37: '山东',
+      41: '河南',
+      42: '湖北 ',
+      43: '湖南',
+      44: '广东',
+      45: '广西',
+      46: '海南',
+      50: '重庆',
+      51: '四川',
+      52: '贵州',
+      53: '云南',
+      54: '西藏 ',
+      61: '陕西',
+      62: '甘肃',
+      63: '青海',
+      64: '宁夏',
+      65: '新疆',
+      71: '台湾',
+      81: '香港',
+      82: '澳门',
+      91: '国外 ',
+    };
+    let row = {
+      pass: true,
+      msg: '验证成功',
+    };
+    if (!code || !/^\d{6}(18|19|20)?\d{2}(0[1-9]|1[012])(0[1-9]|[12]\d|3[01])\d{3}(\d|[xX])$/.test(code)) {
+      row = {
+        pass: false,
+        msg: '身份证号格式错误',
+      };
+    } else if (!city[code.substr(0, 2)]) {
+      row = {
+        pass: false,
+        msg: '身份证号地址编码错误',
+      };
+    } else {
+      // 18位身份证需要验证最后一位校验位
+      if (code.length === 18) {
+        code = code.split('');
+        // ∑(ai×Wi)(mod 11)
+        // 加权因子
+        const factor = [ 7, 9, 10, 5, 8, 4, 2, 1, 6, 3, 7, 9, 10, 5, 8, 4, 2 ];
+        // 校验位
+        const parity = [ 1, 0, 'X', 9, 8, 7, 6, 5, 4, 3, 2 ];
+        let sum = 0;
+        let ai = 0;
+        let wi = 0;
+        for (let i = 0; i < 17; i++) {
+          ai = code[i];
+          wi = factor[i];
+          sum += ai * wi;
+        }
+        if (parity[sum % 11] !== code[17].toUpperCase()) {
+          row = {
+            pass: false,
+            msg: '身份证号校验位错误',
+          };
+        }
+      }
+    }
+    return row;
+  }
+  /**
+   * 检查学生是否参加过这个计划以外的计划,参加过就不让来了
+   * @param {Array} studentList 学生列表
+   * @param {String} planid 计划id
+   */
+  async countStudent(studentList, planid) {
+    let errorcode = '0';
+    const errormsg = [];
+    for (const stu of studentList) {
+      const { name, id_number } = stu;
+      let error = false;
+      let msg = '';
+      const count = await this.ctx.model.Student.count({ name, id_number, planid: { $ne: planid } });
+      if (count > 0) {
+        error = true;
+        msg = `${msg}${name}已经参加过培训`;
+      }
+      if (error) {
+        errorcode = '1';
+        stu.msg = msg;
+        errormsg.push(stu);
+      }
+    }
+
+    return { errorcode, errormsg };
+  }
 }
 
 module.exports = UtilService;