diff --git a/api/app/lib/controllers/member/index.js b/api/app/lib/controllers/member/index.js index b6848dd..4795f92 100644 --- a/api/app/lib/controllers/member/index.js +++ b/api/app/lib/controllers/member/index.js @@ -1,7 +1,7 @@ 'use strict'; const moment = require('moment') -async function add (ctx) { +async function add(ctx) { try { const { models } = ctx.fs.dc; const { @@ -44,7 +44,7 @@ async function add (ctx) { } } -async function edit (ctx) { +async function edit(ctx) { try { const { models } = ctx.fs.dc; const { @@ -84,7 +84,7 @@ async function edit (ctx) { } } -async function searchPepMember (ctx) { +async function searchPepMember(ctx) { try { const { models } = ctx.fs.dc; const { clickHouse } = ctx.app.fs @@ -160,7 +160,7 @@ async function searchPepMember (ctx) { } } -async function del (ctx) { +async function del(ctx) { try { const { models } = ctx.fs.dc; const { pepUserId } = ctx.query @@ -183,7 +183,7 @@ async function del (ctx) { } } -async function list (ctx) { +async function list(ctx) { try { const { models } = ctx.fs.dc; const { judgeHoliday } = ctx.app.fs.utils @@ -307,10 +307,78 @@ async function list (ctx) { } } +async function addMembersBulk(ctx) { + let errorMsg = { message: '批量添加员工信息失败' }; + const transaction = await ctx.fs.dc.orm.transaction(); + try { + const models = ctx.fs.dc.models; + const data = ctx.request.body; + let addMembers = []; + let editMembers = []; + let memberList = await models.Member.findAll({ + attributes: ['pepUserId'] + }); + data.map(d => { + let exist = memberList.find(d => d.pepUserId == d.number);//项企的人员编号字段还没有 + if (exist) { + editMembers.push(d); + } else { + addMembers.push(d); + } + }) + + //处理新增的 + if (addMembers.length) { + await models.Member.bulkCreate(addMembers); + } + + //处理编辑的 + if (editMembers.length) { + for (let i in editMembers) { + let { pepUserId, name, idNumber, gender, birthday, nativePlace, marital, + politicsStatus, phoneNumber, workPlace, graduatedFrom, educationBackground, specialty, graduationDate, + hiredate, turnProbationPeriod, regularDate, dimissionDate, experienceYear, occupationalHistory } = editMembers[i]; + + let dataToUpdate = { + name, + idNumber, + gender, + birthday, + nativePlace, + marital, + politicsStatus, + phoneNumber, + workPlace, + graduatedFrom, + educationBackground, + specialty, + graduationDate, + hiredate, + turnProbationPeriod, + regularDate, + dimissionDate, + experienceYear, + occupationalHistory + } + await models.Member.update(dataToUpdate, { where: { pepUserId: pepUserId } }); + } + } + + await transaction.commit(); + ctx.status = 204; + } catch (error) { + await transaction.rollback(); + ctx.fs.logger.error(`path: ${ctx.path}, error: ${error}`); + ctx.status = 400; + ctx.body = errorMsg; + } +} + module.exports = { add, edit, del, searchPepMember, list, + addMembersBulk }; \ No newline at end of file diff --git a/api/app/lib/routes/member/index.js b/api/app/lib/routes/member/index.js index a3f5d12..fd16bbe 100644 --- a/api/app/lib/routes/member/index.js +++ b/api/app/lib/routes/member/index.js @@ -17,4 +17,8 @@ module.exports = function (app, router, opts) { app.fs.api.logAttr['GET/member/list'] = { content: '查询人员列表', visible: true }; router.get('/member/list', member.list); + + + app.fs.api.logAttr['POST/add/members/bulk'] = { content: '导入员工信息', visible: true }; + router.post('/add/members/bulk', member.addMembersBulk); }; \ No newline at end of file diff --git a/web/client/src/sections/humanAffairs/actions/personnelFiles.js b/web/client/src/sections/humanAffairs/actions/personnelFiles.js index eb926f5..f835534 100644 --- a/web/client/src/sections/humanAffairs/actions/personnelFiles.js +++ b/web/client/src/sections/humanAffairs/actions/personnelFiles.js @@ -2,7 +2,7 @@ import { ApiTable, basicAction } from '$utils' -export function getMemberSearch (query) {//搜索项企用户 +export function getMemberSearch(query) {//搜索项企用户 return (dispatch) => basicAction({ type: "get", dispatch: dispatch, @@ -12,4 +12,15 @@ export function getMemberSearch (query) {//搜索项企用户 msg: { option: "搜索项企用户" }, reducer: { name: "MemberSearch", params: { noClear: true } }, }); +} + +export function membersBulkAdd(values) { + return dispatch => basicAction({ + type: 'post', + dispatch: dispatch, + actionType: 'MEMBER_BULK_ADD', + url: ApiTable.addMembersBulk, + data: values, + msg: { option: '导入员工信息' }, + }); } \ No newline at end of file diff --git a/web/client/src/sections/humanAffairs/containers/import-members-modal.js b/web/client/src/sections/humanAffairs/containers/import-members-modal.js index 82cd629..c0795cc 100644 --- a/web/client/src/sections/humanAffairs/containers/import-members-modal.js +++ b/web/client/src/sections/humanAffairs/containers/import-members-modal.js @@ -1,17 +1,16 @@ 'use strict'; import React, { useState } from 'react'; import { connect } from 'react-redux'; -//import { Button, Input, Card, Modal, Upload, message } from 'antd'; -import { Modal, Form, Button, Upload, Notification } from '@douyinfe/semi-ui'; +import { Modal, Form, Button, Notification } from '@douyinfe/semi-ui'; import { IconUpload } from '@douyinfe/semi-icons'; -//import { Request } from '@peace/utils' -//import request from 'superagent' +import cityData from '../components/city.json'; import XLSX from 'xlsx' -//import { userBulkAdd } from '../../actions' - -//TODO 下载模板和上传文件读取 -const ImportUser = props => { - const { user, dispatch, onCancel } = props +import { membersBulkAdd } from '../actions/personnelFiles' +import ExportJsonExcel from 'js-export-excel'; +import { rule } from './table-input-rule' +//下载模板和上传文件读取 +const ImportMembersModal = props => { + const { dispatch, onCancel } = props const [msg, setMsg] = useState('') const [loading, setLoading] = useState('') const [postData, setPostData] = useState([]) @@ -19,12 +18,13 @@ const ImportUser = props => { const confirm = () => { if (postData.length) { setLoading(true) - // dispatch(userBulkAdd(postData, params.departmentId ? params.departmentId : null)).then(res => { - // if (res.success) { - - // } - // setLoading(false) - // }) + dispatch(membersBulkAdd(postData)).then(res => { + if (res.success) { + Notification.success('导入员工信息成功') + onCancel() + } + setLoading(false) + }) } else { Notification.warn('没有数据可以提交,请上传数据文件') } @@ -32,7 +32,11 @@ const ImportUser = props => { const download = () => { dldTemplate(); - dldText("填写说明.txt", "12121212121212"); + let str = ""; + rule.forEach((v, i) => { + str += `${v}\r\n` + }) + dldText("填写说明.txt", str); } const dldTemplate = () => { @@ -100,9 +104,38 @@ const ImportUser = props => { } } } + request.send(); }) } + const judgePlace = (place) => { + let valid = true; + if (place.split('-').length == 1) {//判断籍贯 + if (['北京市', '上海市', '天津市', '重庆市'].indexOf(place) == -1) { + valid = false; + } + } else if (place.split('-').length == 2) { + let province = place.split('-')[0]; + let city = place.split('-')[1]; + let existProvince = cityData.find(cd => cd.name == province); + if (!existProvince) { + valid = false; + } else { + let existCity = existProvince.children.find(cd => cd.name == city); + if (!existCity) { + valid = false; + } + } + } else { + valid = false; + } + return valid; + } + + const judgeNull = (value) => { + return value ? String(value).trim().replace(/\s*/g, "") : null; + } + return ( { error('请填写至少一行数据') return } - let postData = [] + let postData = []; + const zmsz = /^[A-Za-z0-9]+$/;//字母数字组合 const pattern = /^1[3|4|5|6|7|8|9]\d{9}$/; const sfz = /^[1-9]\d{5}(18|19|([23]\d))\d{2}((0[1-9])|(10|11|12))(([0-2][1-9])|10|20|30|31)\d{3}[0-9Xx]$/;//身份证 + const ymd = /^((19|20)[0-9]{2})[\/\-]((0[1-9])|(1[0-2]))[\/\-]((0[1-9])|((1|2)[0-9])|(3[0-1]))$/;//年月日 + const num0 = /^\d+$/;//正整数+0 for (let i = 0; i < res.length; i++) { - let d = res[i] - let number = String(d['人员编号']).trim(); - let name = String(d['姓名']).trim(); - let idNumber = String(d['证件照']).trim(); - let gender = String(d['性别(男/女)']).trim(); - let birthday = String(d['出生年月日(例2022/02/01)']).trim(); - let nativePlace = String(d['籍贯']).trim(); - let marital = String(d['婚育状态(已婚/未婚/已婚已育)']).trim(); - let politicsStatus = String(d['政治面貌']).trim(); - let phoneNumber = String(d['联系方式']).trim(); - - let workPlace = String(d['工作地点']).trim(); - let graduatedFrom = String(d['毕业院校']).trim(); - let educationBackground = String(d['学历']).trim(); - let specialty = String(d['专业']).trim(); - let graduationDate = String(d['毕业时间']).trim(); - let hiredate = String(d['入职时间']).trim(); - - let turnProbationPeriod = String(d['转试用期时间']).trim(); - let regularDate = String(d['转正时间']).trim(); - let dimissionDate = String(d['离职日期']).trim(); - let experienceYear = String(d['工作经验(年)']).trim(); - let occupationalHistory = String(d['历史工作经历与职务']).trim(); - - //let account = String(d['账号']).trim() - if (!number || !name) { - error(`第${i + 1} 行有空值,请填写后重新上传`) + let d = res[i]; + + let number = judgeNull(d['人员编号']); + let name = judgeNull(d['姓名']); + let idNumber = judgeNull(d['证件号']); + let gender = judgeNull(d['性别(男/女)']); + let birthday = judgeNull(d['出生年月日(例2022/02/01)']); + let nativePlace = judgeNull(d['籍贯']); + let marital = judgeNull(d['婚育状态(已婚/未婚/已婚已育)']); + + let politicsStatus = judgeNull(d['政治面貌']); + let phoneNumber = judgeNull(d['联系方式']); + let workPlace = judgeNull(d['工作地点']); + let graduatedFrom = judgeNull(d['毕业院校']); + let educationBackground = judgeNull(d['学历']); + let specialty = judgeNull(d['专业']); + let graduationDate = judgeNull(d['毕业时间']); + + let hiredate = judgeNull(d['入职时间']); + let turnProbationPeriod = judgeNull(d['转试用期时间']); + let regularDate = judgeNull(d['转正时间']); + let dimissionDate = judgeNull(d['离职日期']); + let experienceYear = judgeNull(d['工作经验(年)']); + let occupationalHistory = judgeNull(d['历史工作经历与职务']); + + if (!number) {//人员编号不为空,唯一,字母和数字 + error(`第${i + 1}行人员编号为空,请填写`) return } - if (!sfz.test(idcard)) { - error(`第${i + 1} 行证件号错误`) + if (postData.some(p => p.number == number)) {//人员编号 唯一 + error(`第${i + 1}行人员编号重复,请更改后重新上传`) + return + } + if (!zmsz.test(number)) { + error(`第${i + 1}行人员编号错误,请填写字母和数字的组合`) + return + } + if (!name) {//姓名必填 + error(`第${i + 1}行姓名为空,请填写`) + return + } + if (!sfz.test(idNumber)) { + error(`第${i + 1}行证件号错误`) + return + } + if (['男', '女'].indexOf(gender) == -1) { + error(`第${i + 1}行性别错误`) + return + } + // if (!ymd.test(birthday)) { + // error(`第${i + 1}行出生年月日错误,请填写yyyy/mm/dd格式`) + // return + // } + + let jgValid = judgePlace(nativePlace); + if (!jgValid) { + error(`第${i + 1}行籍贯错误`) + return + } + + if (['已婚', '未婚', '已婚已育'].indexOf(marital) == -1) { + error(`第${i + 1}行婚育状态错误`) + return + } + if (['群众', '党员'].indexOf(politicsStatus) == -1) { + error(`第${i + 1}行政治面貌错误`) return } if (!pattern.test(phoneNumber)) { - error(`第${i + 1} 行手机号码错误`) + error(`第${i + 1}行联系方式错误`) + return + } + let wpValid = judgePlace(workPlace); + if (!wpValid) { + error(`第${i + 1}行工作地点错误`) return } - if (name.length > 128 || account.length > 128) { - error(`第${i + 1} 行数据字符长度大于 128,请更改后重新上传`) + if (['小学', '初中', '高中', '大专', '本科', '研究生', '博士'].indexOf(educationBackground) == -1) { + error(`第${i + 1}行学历错误`) return } - if (postData.some(p => p.number == number)) { - error(`第${i + 1} 行人员编号重复,请更改后重新上传`) + // if (!ymd.test(graduationDate)) { + // error(`第${i + 1}行毕业时间错误,请填写yyyy/mm/dd格式`) + // return + // } + // if (!ymd.test(hiredate)) { + // error(`第${i + 1}行入职时间错误,请填写yyyy/mm/dd格式`) + // return + // } + // if (!ymd.test(turnProbationPeriod)) { + // error(`第${i + 1}行转试用期时间错误,请填写yyyy/mm/dd格式`) + // return + // } + // if (!ymd.test(regularDate)) { + // error(`第${i + 1}行转正时间错误,请填写yyyy/mm/dd格式`) + // return + // } + // if (!ymd.test(dimissionDate)) { + // error(`第${i + 1}行离职日期错误,请填写yyyy/mm/dd格式`) + // return + // } + if (!num0.test(experienceYear)) { + error(`第${i + 1}行工作经验(年)错误,请填写非负整数`) return } - postData.push({ - name, phoneNumber, account + postData.push({//人员编号 待办todotodo + pepUserId: 555, name, idNumber, gender, birthday, nativePlace, marital, + politicsStatus, phoneNumber, workPlace, graduatedFrom, educationBackground, specialty, graduationDate, + hiredate, turnProbationPeriod, regularDate, dimissionDate, experienceYear, occupationalHistory }) } if (postData.length) { @@ -196,12 +294,12 @@ const ImportUser = props => { setMsg(msg) onSuccess({ message: msg }) }) - }} - > + }}> + {msg}
最大不超过200M,导入文件需与 download()} style={{ cursor: 'pointer', color: '#0066FF' }}>导入模板 一致
@@ -211,10 +309,10 @@ const ImportUser = props => { } function mapStateToProps(state) { - const { auth, customizeList } = state; + const { auth } = state; return { user: auth.user, } } -export default connect(mapStateToProps)(ImportUser); \ No newline at end of file +export default connect(mapStateToProps)(ImportMembersModal); \ No newline at end of file diff --git a/web/client/src/sections/humanAffairs/containers/table-input-rule.js b/web/client/src/sections/humanAffairs/containers/table-input-rule.js new file mode 100644 index 0000000..b1a9a98 --- /dev/null +++ b/web/client/src/sections/humanAffairs/containers/table-input-rule.js @@ -0,0 +1,10 @@ +export const rule = [ + "人员编号:必填,唯一,数字和字母的组合;", + "姓名:必填,若导入数据的姓名与编号对应的项企姓名不同,将以项企数据为准;", + "日期:(年/月/日,例2022/02/01);", + "地址:格式为“省-市”,如“江苏省-镇江市”,直辖市直接显示市,如“北京市”;", + "政治面貌:党员或群众;", + "学历:小学、初中、高中、大专、本科、研究生或博士;", + "工作经验:0或正整数;", + "没有的数据可不填写。" +] \ No newline at end of file diff --git a/web/client/src/utils/webapi.js b/web/client/src/utils/webapi.js index 760d4fb..0bc8f48 100644 --- a/web/client/src/utils/webapi.js +++ b/web/client/src/utils/webapi.js @@ -18,6 +18,7 @@ export const ApiTable = { //人事管理-人员档案 getMemberSearch: 'member/search',//搜索项企用户 + addMembersBulk: 'add/members/bulk' }; export const RouteTable = {