|
@@ -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, '批次不能为空');
|
|
|
+
|
|
|
+ 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;
|
|
|
+
|
|
|
+ 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,
|
|
|
+ '该计划该期该批次有学生已经安排班级或寝室 无法进行导入,请对该批次学生进行修改重新导入!'
|
|
|
+ );
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ 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;
|
|
|
+ let studentList = await this.getDataFromExcel(fullUrl);
|
|
|
+ const checkRes = await this.checkData(studentList);
|
|
|
+ const { errorcode } = checkRes;
|
|
|
+ if (errorcode === '1') {
|
|
|
+ return checkRes;
|
|
|
+ }
|
|
|
+
|
|
|
+ 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';
|
|
|
+ }
|
|
|
+
|
|
|
+ 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!`);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ });
|
|
|
+
|
|
|
+ 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, '');
|
|
|
+
|
|
|
+ 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 };
|
|
|
+ }
|
|
|
+
|
|
|
+ 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) {
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+ 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 {
|
|
|
+
|
|
|
+ if (code.length === 18) {
|
|
|
+ code = code.split('');
|
|
|
+
|
|
|
+
|
|
|
+ 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;
|