diff --git a/api/app/lib/controllers/member/index.js b/api/app/lib/controllers/member/index.js index 37d5b84..3b6c8fa 100644 --- a/api/app/lib/controllers/member/index.js +++ b/api/app/lib/controllers/member/index.js @@ -2,7 +2,7 @@ const moment = require('moment') const fs = require('fs'); -async function add (ctx) { +async function add(ctx) { try { const { models } = ctx.fs.dc; const { @@ -45,7 +45,7 @@ async function add (ctx) { } } -async function edit (ctx) { +async function edit(ctx) { try { const { models } = ctx.fs.dc; const { @@ -85,7 +85,7 @@ async function edit (ctx) { } } -async function searchPepMember (ctx) { +async function searchPepMember(ctx) { try { const { models } = ctx.fs.dc; const { clickHouse } = ctx.app.fs @@ -103,7 +103,8 @@ async function searchPepMember (ctx) { const userRes = await clickHouse.pepEmis.query(` SELECT user.id AS pepUserId, - user.people_code AS userCode, + user.people_code AS userCode, + basicdata_post.name AS userPost, user.name AS userName, role.name AS roleName, role.id AS roleId, @@ -115,6 +116,8 @@ async function searchPepMember (ctx) { ON user_role.user = user.id LEFT JOIN role ON role.id = user_role.role + LEFT JOIN basicdata_post + ON basicdata_post.id = user.post LEFT JOIN department_user ON department_user.user = user.id LEFT JOIN department @@ -146,6 +149,7 @@ async function searchPepMember (ctx) { pepUserId: u.pepUserId, name: u.userName, userCode: u.userCode, + userPost: u.userPost, departmrnt: u.depId ? [{ id: u.depId, name: u.depName @@ -153,7 +157,7 @@ async function searchPepMember (ctx) { role: u.roleId ? [{ id: u.roleId, name: u.roleName - }] : [], + }] : [] }) } }) @@ -169,7 +173,7 @@ async function searchPepMember (ctx) { } } -async function del (ctx) { +async function del(ctx) { try { const { models } = ctx.fs.dc; const { pepUserId } = ctx.query @@ -181,7 +185,13 @@ async function del (ctx) { pepUserId, } }) - + await models.SalesDistribution.update({//顺便把销售人员删了 + del: true, + }, { + where: { + pepUserId, + } + }) ctx.status = 204; } catch (error) { ctx.fs.logger.error(`path: ${ctx.path}, error: ${error}`); @@ -192,7 +202,7 @@ async function del (ctx) { } } -async function nativePlaceList (ctx) { +async function nativePlaceList(ctx) { // 获取已有的户籍地列表 try { const { models } = ctx.fs.dc; @@ -216,7 +226,7 @@ async function nativePlaceList (ctx) { } } -async function workPlaceList (ctx) { +async function workPlaceList(ctx) { // 获取已有的工作地列表 try { const { models } = ctx.fs.dc; @@ -240,7 +250,7 @@ async function workPlaceList (ctx) { } } -async function maritalList (ctx) { +async function maritalList(ctx) { // 获取已有的婚育状况列表 try { const { models } = ctx.fs.dc; @@ -264,7 +274,7 @@ async function maritalList (ctx) { } } -async function list (ctx) { +async function list(ctx) { try { const { models } = ctx.fs.dc; const { judgeHoliday, memberList, packageUserData } = ctx.app.fs.utils @@ -299,7 +309,7 @@ async function list (ctx) { } } -async function overTimeStatistics (ctx) { +async function overTimeStatistics(ctx) { try { const { models } = ctx.fs.dc; const { clickHouse } = ctx.app.fs @@ -375,7 +385,7 @@ async function overTimeStatistics (ctx) { let returnD = { ...(statisticRes.length ? statisticRes[0] : {}), data: dataRes, - dayStatisticData: statisticDayRes.sort((a,b)=>moment(a.day)-moment(b.day)) + dayStatisticData: statisticDayRes.sort((a, b) => moment(a.day) - moment(b.day)) } ctx.status = 200; ctx.body = returnD @@ -388,7 +398,7 @@ async function overTimeStatistics (ctx) { } } -async function vacateStatistics (ctx) { +async function vacateStatistics(ctx) { try { const { models } = ctx.fs.dc; const { clickHouse } = ctx.app.fs @@ -465,7 +475,7 @@ async function vacateStatistics (ctx) { let returnD = { statistic: statisticRes, data: dataRes, - dayStatisticData: statisticDayRes.sort((a,b)=>moment(a.day)-moment(b.day)) + dayStatisticData: statisticDayRes.sort((a, b) => moment(a.day) - moment(b.day)) } ctx.status = 200; ctx.body = returnD @@ -478,11 +488,11 @@ async function vacateStatistics (ctx) { } } -async function exportData (ctx) { +async function exportData(ctx) { try { const { models } = ctx.fs.dc; const { clickHouse, opts: { qiniu } } = ctx.app.fs - const { simpleExcelDown, memberList, packageUserData } = ctx.app.fs.utils + const { simpleExcelDown, memberList, packageUserData, UserAttribute } = ctx.app.fs.utils const { keywordTarget, keyword, limit, page, state, keys = '', hiredateStart, hiredateEnd, marital, native, workPlace, @@ -507,7 +517,28 @@ async function exportData (ctx) { }, { title: '姓名', key: 'userName', - }] + }, { + title: '所属部门', + key: 'departmrnt', + }, { + title: '职位', + key: 'userJob', + }, { + title: '岗位', + key: 'userPost', + }, { + title: '在职状态', + key: 'userActiveStatus', + }, { + title: '绩点', + key: 'point', + }, { + title: '归属机构', + key: 'userOrganization', + }, { + title: '技术职级等级', + key: 'technicalGrade', + }] let header = [].concat(preHeader) for (let k in tableAttributes) { const comment = tableAttributes[k].comment @@ -569,6 +600,10 @@ async function exportData (ctx) { exportD.forEach(d => { d.departmrnt = d.departmrnt.map(dep => dep.name).join('、') d.role = d.role.map(r => r.name).join('、') + + d.userJob = d.userJob? UserAttribute.jobDataSource[d.userJob - 1] : ''; + d.userActiveStatus = d.userActiveStatus? UserAttribute.activeStatusDataSource[d.userActiveStatus - 1] : ''; + d.userOrganization = d.userOrganization? UserAttribute.organizationDataSource[d.userOrganization - 1] : ''; d.idPhoto ? d.idPhoto = qiniu.domain + '/' + d.idPhoto : '' d.vitae ? d.vitae = qiniu.domain + '/' + d.vitae : '' @@ -598,7 +633,7 @@ async function exportData (ctx) { } } -async function addMembersBulk (ctx) { +async function addMembersBulk(ctx) { let errorMsg = { message: '导入员工信息失败' }; const transaction = await ctx.fs.dc.orm.transaction(); try { diff --git a/api/app/lib/controllers/salesDistribution/index.js b/api/app/lib/controllers/salesDistribution/index.js new file mode 100644 index 0000000..7da2d1f --- /dev/null +++ b/api/app/lib/controllers/salesDistribution/index.js @@ -0,0 +1,336 @@ +'use strict'; +const moment = require('moment') +const fs = require('fs'); + +async function salesList(ctx) { + try { + const { memberList, packageUserData } = ctx.app.fs.utils + const { + keywordTarget, keyword, limit, page, state, + hiredateStart, hiredateEnd, marital, native, workPlace, + orderBy, orderDirection, placeSearch + } = ctx.query + + const userRes = await memberList({ + keywordTarget, keyword, limit: '', page: '', state, + hiredateStart, hiredateEnd, marital, native, workPlace, + orderBy, orderDirection, + nowAttendanceTime: true + }) + + let { packageUser: members } = await packageUserData(userRes, { + state: true, + }) + + const { models } = ctx.fs.dc; + + let mIds = members.map(m => m.pepUserId); + + + let where = { + del: false, + pepUserId: { $in: mIds } + } + if (placeSearch) { + where.$or = [{ + provinces: { $like: `%${placeSearch}%` } + }, { + cities: { $like: `%${placeSearch}%` } + }] + } + let res = await models.SalesDistribution.findAndCountAll({ + where: where, + offset: Number(page) * Number(limit), + limit: Number(limit), + order: [['id', 'DESC']] + }) + + let rslt = [] + res.rows.map(d => { + let info = members.find(m => m.pepUserId == d.dataValues.pepUserId); + let item = { + name: info.userName, + userCode: info.userCode, + post: info.userPost, + department: info.departmrnt, + hireDate: info.hiredate,//入职时间 + regularDate: info.regularDate,//转正时间 + ...d.dataValues + } + rslt.push(item); + }) + ctx.status = 200; + ctx.body = { + count: res.count, + rows: rslt + }; + } catch (error) { + ctx.fs.logger.error(`path: ${ctx.path}, error: ${error}`); + ctx.status = 400; + ctx.body = { + message: typeof error == 'string' ? error : undefined + } + } +} + +async function add(ctx) { + try { + const { models } = ctx.fs.dc; + const { pepUserId, provinces, cities, businessLines } = ctx.request.body + + const existRes = await models.SalesDistribution.findOne({ + where: { pepUserId } + }) + + if (existRes && !existRes.del) { + throw '当前销售人员信息已存在' + } + + let storageData = { pepUserId, provinces, cities, businessLines, del: false } + if (existRes && existRes.del) { + await models.SalesDistribution.update(storageData, { + where: { pepUserId } + }) + } else { + await models.SalesDistribution.create(storageData) + } + ctx.status = 204; + } catch (error) { + ctx.fs.logger.error(`path: ${ctx.path}, error: ${error}`); + ctx.status = 400; + ctx.body = { + message: typeof error == 'string' ? error : undefined + } + } +} + +async function edit(ctx) { + try { + const { models } = ctx.fs.dc; + const { pepUserId, provinces, cities, businessLines } = ctx.request.body + + const existRes = await models.SalesDistribution.findOne({ + where: { pepUserId } + }) + + if (!existRes) { + throw '当前销售人员信息不存在' + } + + let storageData = { pepUserId, provinces, cities, businessLines, del: false } + + await models.SalesDistribution.update(storageData, { + where: { + pepUserId: pepUserId + } + }) + + ctx.status = 204; + } catch (error) { + ctx.fs.logger.error(`path: ${ctx.path}, error: ${error}`); + ctx.status = 400; + ctx.body = { + message: typeof error == 'string' ? error : undefined + } + } +} + +async function del(ctx) { + try { + const { models } = ctx.fs.dc; + const { pepUserId } = ctx.query + + await models.SalesDistribution.update({ + del: true, + }, { + where: { + pepUserId, + } + }) + ctx.status = 204; + } catch (error) { + ctx.fs.logger.error(`path: ${ctx.path}, error: ${error}`); + ctx.status = 400; + ctx.body = { + message: typeof error == 'string' ? error : undefined + } + } +} + +// async function exportData(ctx) { +// try { +// const { models } = ctx.fs.dc; +// const { clickHouse, opts: { qiniu } } = ctx.app.fs +// const { simpleExcelDown, memberList, packageUserData } = ctx.app.fs.utils +// const { +// keywordTarget, keyword, limit, page, state, keys = '', +// hiredateStart, hiredateEnd, marital, native, workPlace, +// orderBy, orderDirection, +// } = ctx.query + +// const userRes = await memberList({ +// keywordTarget, keyword, limit, page, state, +// hiredateStart, hiredateEnd, marital, native, workPlace, +// orderBy, orderDirection, +// nowAttendanceTime: true +// }) + +// const tableAttributes = models['Member'].tableAttributes +// const optionKeys = keys.split(',') + +// let { packageUser: exportD, pepUserIds } = await packageUserData(userRes) + +// let preHeader = [{ +// title: '员工编号', +// key: 'userCode', +// }, { +// title: '姓名', +// key: 'userName', +// }] +// let header = [].concat(preHeader) +// for (let k in tableAttributes) { +// const comment = tableAttributes[k].comment +// if (k != 'id' && k != 'pepUserId' && comment) { +// if ([].includes(k)) { +// // 截住不想导出的字段 +// continue +// } +// header.push({ +// title: comment || '-', +// key: k, +// // index: tableAttributes[k].index, +// }) +// } +// } + +// if (optionKeys.includes('overtimeStatistic')) { +// header = header.concat([{ +// title: '累计加班次数', +// key: 'overTimeCount', +// }, { +// title: '累计加班总时长 / h', +// key: 'overTimeDuration', +// },]) +// } +// if (optionKeys.includes('vacateStatistic')) { +// header = header.concat([{ +// title: '累计请假次数', +// key: 'vacateCount', +// }, { +// title: '累计请假总时长 / h', +// key: 'vacateDuration', +// },]) +// } + +// // 查询累计加班次数及总时长 +// const statisticOvertimeRes = await clickHouse.hr.query(` +// SELECT +// pep_user_id AS pepUserId, +// count(id) AS count, +// sum(duration) AS duration +// FROM +// overtime +// WHERE pep_user_id IN (${pepUserIds.join(',')}) +// GROUP BY pep_user_id +// `).toPromise() + +// const statisticVacateRes = await clickHouse.hr.query(` +// SELECT +// pep_user_id AS pepUserId, +// count(id) AS count, +// sum(duration) AS duration +// FROM +// vacate +// WHERE pep_user_id IN (${pepUserIds.join(',')}) +// GROUP BY pep_user_id +// `).toPromise() + +// exportD.forEach(d => { +// d.departmrnt = d.departmrnt.map(dep => dep.name).join('、') +// d.role = d.role.map(r => r.name).join('、') + +// d.idPhoto ? d.idPhoto = qiniu.domain + '/' + d.idPhoto : '' +// d.vitae ? d.vitae = qiniu.domain + '/' + d.vitae : '' + +// const corOverTime = statisticOvertimeRes.find(so => so.pepUserId == d.pepUserId) +// d.overTimeCount = corOverTime ? corOverTime.count : 0 +// d.overTimeDuration = corOverTime ? (corOverTime.duration / 3600).toFixed(1) : 0 +// const corVacate = statisticVacateRes.find(so => so.pepUserId == d.pepUserId) +// d.vacateCount = corVacate ? corVacate.count : 0 +// d.vacateDuration = corVacate ? (corVacate.duration / 3600).toFixed(1) : 0 +// }) + +// const fileName = `人员信息_${moment().format('YYYYMMDDHHmmss')}` + '.csv' +// const filePath = await simpleExcelDown({ data: exportD, header, fileName: fileName }) +// const fileData = fs.readFileSync(filePath); + +// ctx.status = 200; +// ctx.set('Content-Type', 'application/x-xls'); +// ctx.set('Content-disposition', 'attachment; filename=' + encodeURI(fileName)); +// ctx.body = fileData; +// } catch (error) { +// ctx.fs.logger.error(`path: ${ctx.path}, error: ${error}`); +// ctx.status = 400; +// ctx.body = { +// message: typeof error == 'string' ? error : undefined +// } +// } +// } + +async function addSalesMemberBulk(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 addArr = []; + let editArr = []; + let list = await models.SalesDistribution.findAll({ + attributes: ['pepUserId'] + }); + data.map(d => { + let exist = list.find(m => m.pepUserId == d.pepUserId);//项企的人员编号字段还没有 + if (exist) { + editArr.push(d); + } else { + addArr.push(d); + } + }) + + //处理新增的 + if (addArr.length) { + await models.SalesDistribution.bulkCreate(addArr); + } + + //处理编辑的 + if (editArr.length) { + for (let i in editArr) { + let { pepUserId, provinces, cities, businessLines, del = false } = editArr[i]; + + let dataToUpdate = { + provinces, + cities, + businessLines, + del + } + await models.SalesDistribution.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 = { + salesList, + add, + edit, + del, + //exportData, + addSalesMemberBulk, +} \ No newline at end of file diff --git a/api/app/lib/models/sales_distribution.js b/api/app/lib/models/sales_distribution.js new file mode 100644 index 0000000..1d46830 --- /dev/null +++ b/api/app/lib/models/sales_distribution.js @@ -0,0 +1,71 @@ +/* eslint-disable*/ + +'use strict'; + +module.exports = dc => { + const DataTypes = dc.ORM; + const sequelize = dc.orm; + const SalesDistribution = sequelize.define("salesDistribution", { + id: { + type: DataTypes.INTEGER, + allowNull: false, + defaultValue: null, + comment: null, + primaryKey: true, + field: "id", + autoIncrement: true, + unique: "sales_distribution_id_uindex" + }, + pepUserId: { + type: DataTypes.INTEGER, + allowNull: false, + defaultValue: null, + comment: null, + primaryKey: false, + field: "pep_user_id", + autoIncrement: false + }, + provinces: { + type: DataTypes.STRING, + allowNull: false, + defaultValue: null, + comment: null, + primaryKey: false, + field: "provinces", + autoIncrement: false + }, + cities: { + type: DataTypes.STRING, + allowNull: true, + defaultValue: null, + comment: null, + primaryKey: false, + field: "cities", + autoIncrement: false + }, + del: { + type: DataTypes.BOOLEAN, + allowNull: true, + defaultValue: null, + comment: null, + primaryKey: false, + field: "del", + autoIncrement: false + }, + businessLines: { + type: DataTypes.STRING, + allowNull: true, + defaultValue: null, + comment: null, + primaryKey: false, + field: "business_lines", + autoIncrement: false + } + }, { + tableName: "sales_distribution", + comment: "", + indexes: [] + }); + dc.models.SalesDistribution = SalesDistribution; + return SalesDistribution; +}; \ No newline at end of file diff --git a/api/app/lib/routes/salesDistribution/index.js b/api/app/lib/routes/salesDistribution/index.js new file mode 100644 index 0000000..28ed0e1 --- /dev/null +++ b/api/app/lib/routes/salesDistribution/index.js @@ -0,0 +1,24 @@ +'use strict'; + +const salesDistribution = require('../../controllers/salesDistribution'); + +module.exports = function (app, router, opts) { + + app.fs.api.logAttr['GET/sales/member/list'] = { content: '查询销售人员列表', visible: true }; + router.get('/sales/member/list', salesDistribution.salesList); + + app.fs.api.logAttr['POST/sales/member/add'] = { content: '添加销售人员信息', visible: true }; + router.post('/sales/member/add', salesDistribution.add); + + app.fs.api.logAttr['PUT/sales/member/modify'] = { content: '编辑销售人员信息', visible: true }; + router.put('/sales/member/modify', salesDistribution.edit); + + app.fs.api.logAttr['DEL/sales/member/del'] = { content: '删除销售人员信息', visible: true }; + router.del('/sales/member/del', salesDistribution.del); + + app.fs.api.logAttr['POST/add/sales/members/bulk'] = { content: '导入销售人员信息', visible: true }; + router.post('/add/sales/members/bulk', salesDistribution.addSalesMemberBulk); + + // app.fs.api.logAttr['GET/sales/members/export'] = { content: '导出销售人员信息', visible: true }; + // router.get('/sales/members/export', salesDistribution.exportData); +}; \ No newline at end of file diff --git a/api/app/lib/utils/constant.js b/api/app/lib/utils/constant.js index 8012eca..67f58e8 100644 --- a/api/app/lib/utils/constant.js +++ b/api/app/lib/utils/constant.js @@ -2,19 +2,25 @@ module.exports = function (app, opts) { - const dayType = { - dayoff: '普假', - workday: '工作日', - festivals: '法定假', - } + const dayType = { + dayoff: '普假', + workday: '工作日', + festivals: '法定假', + } - const overtimeType = { - '发放加班补偿': '折算', - '调休': '调休' - } - - return { - dayType, - overtimeType, - } + const overtimeType = { + '发放加班补偿': '折算', + '调休': '调休' + } + const UserAttribute = { + jobDataSource: ['普通员工', '中层', '高层'], + activeStatusDataSource: ['在职', '离职', '特殊状态-特殊账号'], + organizationDataSource: ['江西飞尚科技有限公司', '江西飞尚工程质量检测有限公司', + '江西飞尚科技有限公司江苏分公司', '江西汇派科技有限公司'], + }; + return { + dayType, + overtimeType, + UserAttribute + } } \ No newline at end of file diff --git a/api/app/lib/utils/member.js b/api/app/lib/utils/member.js index 479d521..1a5c404 100644 --- a/api/app/lib/utils/member.js +++ b/api/app/lib/utils/member.js @@ -200,13 +200,11 @@ module.exports = function (app, opts) { `: ''} WHERE member.del = '0' - ${keywordTarget == 'role' && keyword ? ` - AND user.id IN ( - SELECT user_role.user - FROM ${pepEmis}.user_role AS user_role - INNER JOIN ${pepEmis}.role AS role - ON role.id = user_role.role - AND role.name LIKE '%${keyword}%' + ${keywordTarget == 'post' && keyword ? ` + AND user.post IN ( + SELECT basicDataPost.id + FROM ${pepEmis}.basicdata_post AS basicDataPost + where basicDataPost.name LIKE '%${keyword}%' ) ` : ''} ${keywordTarget == 'dep' && keyword ? ` @@ -244,10 +242,14 @@ module.exports = function (app, opts) { hrMember.*, user.name AS userName, user.people_code AS userCode, + basicDataPost.name AS userPost, role.name AS roleName, role.id AS roleId, department.name AS depName, - department.id AS depId + department.id AS depId, + user.job AS userJob, + user.active_status AS userActiveStatus, + user.organization AS userOrganization FROM ( SELECT ${orderBy == 'overtimeTakeRestSum' @@ -287,6 +289,8 @@ module.exports = function (app, opts) { ON ${pepEmis}.user_role.user = user.id LEFT JOIN ${pepEmis}.role AS role ON ${pepEmis}.role.id = user_role.role + LEFT JOIN ${pepEmis}.basicdata_post AS basicDataPost + ON ${pepEmis}.basicdata_post.id = user.post LEFT JOIN ${pepEmis}.department_user AS department_user ON department_user.user = user.id LEFT JOIN ${pepEmis}.department AS department diff --git a/api/app/lib/utils/xlsxDownload.js b/api/app/lib/utils/xlsxDownload.js index bcd060a..21f8fea 100644 --- a/api/app/lib/utils/xlsxDownload.js +++ b/api/app/lib/utils/xlsxDownload.js @@ -59,7 +59,7 @@ module.exports = function (app, opts) { indexCell.style = headerStyle for (let h of header) { const cell = row.addCell(); - cell.value = data[i][h.key] || h.defaultValue || ''; + cell.value = data[i][h.key] || h.defaultValue || '-'; cell.style = style } } diff --git a/doc/scripts/0.0.4/schema/1.sales_distribution.sql b/doc/scripts/0.0.4/schema/1.sales_distribution.sql new file mode 100644 index 0000000..28d3160 --- /dev/null +++ b/doc/scripts/0.0.4/schema/1.sales_distribution.sql @@ -0,0 +1,11 @@ + + +CREATE TABLE sales_distribution +( + id serial PRIMARY KEY NOT NULL, + pep_user_id int NOT NULL, + provinces text NOT NULL, + cities text, + del boolean DEFAULT false NULL +); +CREATE UNIQUE INDEX sales_distribution_id_uindex ON sales_distribution (id); \ No newline at end of file diff --git a/doc/scripts/PEP V3.0.0/schema/1.sales_distribution_modify.sql b/doc/scripts/PEP V3.0.0/schema/1.sales_distribution_modify.sql new file mode 100644 index 0000000..82a1085 --- /dev/null +++ b/doc/scripts/PEP V3.0.0/schema/1.sales_distribution_modify.sql @@ -0,0 +1,4 @@ + + +alter table sales_distribution + add "business_lines" text; \ No newline at end of file diff --git a/web/client/index.ejs b/web/client/index.ejs index 488bfac..fb765cf 100644 --- a/web/client/index.ejs +++ b/web/client/index.ejs @@ -9,7 +9,7 @@ - + diff --git a/web/client/index.html b/web/client/index.html index 9474515..45f5de1 100644 --- a/web/client/index.html +++ b/web/client/index.html @@ -7,7 +7,7 @@ - + diff --git a/web/client/src/layout/components/header/contant.js b/web/client/src/layout/components/header/contant.js index b58ed2d..4aa17a0 100644 --- a/web/client/src/layout/components/header/contant.js +++ b/web/client/src/layout/components/header/contant.js @@ -60,6 +60,11 @@ const headerItems = [{ itemKey: "recruitRecord", text: "招聘记录", to: "/humanAffairs/recruit/recruitRecord/appointmentRecords" + }, { + fatherKey: "recruit", + itemKey: "salesStatistics", + text: "销售统计", + to: "/humanAffairs/recruit/salesStatistics/personnelDistribution" }] }, { itemKey: "employeeRelationship", diff --git a/web/client/src/sections/humanAffairs/actions/index.js b/web/client/src/sections/humanAffairs/actions/index.js index f2d429b..d69a9d8 100644 --- a/web/client/src/sections/humanAffairs/actions/index.js +++ b/web/client/src/sections/humanAffairs/actions/index.js @@ -2,8 +2,9 @@ import * as personnelFiles from './personnelFiles' import * as employeeInformation from './employeeInformation' - +import * as salesDistribution from './salesDistribution' export default { ...personnelFiles, - ...employeeInformation + ...employeeInformation, + ...salesDistribution } \ No newline at end of file diff --git a/web/client/src/sections/humanAffairs/actions/salesDistribution.js b/web/client/src/sections/humanAffairs/actions/salesDistribution.js new file mode 100644 index 0000000..6037d8c --- /dev/null +++ b/web/client/src/sections/humanAffairs/actions/salesDistribution.js @@ -0,0 +1,89 @@ +'use strict'; + +import { ApiTable, basicAction } from '$utils' + +export function addSalesMemberBulk(values) { + return dispatch => basicAction({ + type: 'post', + dispatch: dispatch, + actionType: 'SALES_MEMBER_BULK_ADD', + url: ApiTable.addSalesMemberBulk, + data: values, + msg: { option: '导入销售人员信息' }, + }); +} + +export function postSalesMember(data) {//添加/编辑 + let msg = '' + if (data) { + msg = data.msg + } + return (dispatch) => + basicAction({ + type: "post", + dispatch: dispatch, + data, + actionType: "POST_SALES_MEMBER", + url: `${ApiTable.addSalesMember}`, + msg: { option: msg }, //添加/编辑 + reducer: { name: "" }, + }); +} + +export function getSalesList(query) {//查询 + return (dispatch) => basicAction({ + type: "get", + dispatch: dispatch, + actionType: "GET_SALES_MENBER_LIST", + query: query, + url: `${ApiTable.getSalesList}`, + msg: { option: "查询销售人员列表" }, + reducer: { name: "SalesMemberList", params: { noClear: true } }, + }); +} +export function delSalesMember(data) {//删除 + let msg = '' + if (data) { + msg = data.msg + } + return (dispatch) => + basicAction({ + type: "del", + query: data, + dispatch: dispatch, + actionType: "DEL_SALES_MEMBER", + url: `${ApiTable.delSalesMember}`, + msg: { option: msg }, //删除 + reducer: {}, + }); +} + +// export function getMemberExport(query) {//导出员工信息 +// return (dispatch) => basicAction({ +// type: "get", +// dispatch: dispatch, +// actionType: "GET_MemberEXPORT", +// query: query, +// url: `${ApiTable.getMemberExport}`, +// msg: { option: "导出员工信息" }, +// reducer: { name: "MemberExport", params: { noClear: true } }, +// }); +// } + + +export function editSalesMember(data) {//更新 + let msg = '' + if (data) { + msg = data.msg + } + return (dispatch) => + basicAction({ + type: "put", + dispatch: dispatch, + data, + actionType: "PUT_SALES_MEMBER", + url: `${ApiTable.editSalesMember}`, + msg: { option: msg }, //更新 + reducer: {}, + }); +} \ No newline at end of file diff --git a/web/client/src/sections/humanAffairs/components/personnelModal.jsx b/web/client/src/sections/humanAffairs/components/personnelModal.jsx index 425d835..698efb0 100644 --- a/web/client/src/sections/humanAffairs/components/personnelModal.jsx +++ b/web/client/src/sections/humanAffairs/components/personnelModal.jsx @@ -240,33 +240,10 @@ function pushModal (props) {
- 职位: + 岗位:
- { - peoplePro.role.map((ite, idx) => { - let roleArr = [] - for (let i = 0; i < peoplePro.role.length; i++) { - roleArr.push(peoplePro.role[i].name) - } - return ( -
- {idx == 0 ? - (ite.name) : ('') - } - { - peoplePro.role.length > 1 && idx == 1 ? ( - -
- +{peoplePro.role.length - 1} -
-
- ) : ('') - } -
- ) - }) - } + {peoplePro.userPost || '-'}
diff --git a/web/client/src/sections/humanAffairs/containers/employeeInformation.jsx b/web/client/src/sections/humanAffairs/containers/employeeInformation.jsx index f0691ad..1fcdeff 100644 --- a/web/client/src/sections/humanAffairs/containers/employeeInformation.jsx +++ b/web/client/src/sections/humanAffairs/containers/employeeInformation.jsx @@ -7,6 +7,7 @@ import '../style.less' import { Setup } from "$components"; import moment from 'moment' import { set } from 'nprogress'; +import { UserAttribute } from '$utils' const employeeInformation = (props) => { const { dispatch, actions, history, user, loading, socket, xqMembers } = props @@ -33,7 +34,12 @@ const employeeInformation = (props) => { list: [ { name: "姓名", value: "userName" }, { name: "所属部门", value: "departmrnt" }, - { name: "职位", value: "roleName" }, + { name: "职位", value: "userJob" }, + { name: "岗位", value: "userPost" }, + { name: "在职状态", value: "userActiveStatus" }, + { name: "绩点", value: "point" }, + { name: "所属机构", value: "userOrganization" }, + { name: "技术职级等级", value: "technicalGrade" }, { name: "证件号", value: "idNumber" }, { name: "性别", value: "gender" }, { name: "籍贯", value: "nativePlace" }, @@ -77,7 +83,8 @@ const employeeInformation = (props) => { localStorage.getItem(EMPLOYEEINFORMATION) == null ? localStorage.setItem( EMPLOYEEINFORMATION, - JSON.stringify(['userName', 'departmrnt', 'roleName', 'hiredate', 'age', 'phoneNumber', 'marital', 'politicsStatus', 'educationBackground', + JSON.stringify(['userName', 'departmrnt', 'userJob', 'userPost', 'userActiveStatus', 'point', 'userOrganization', 'technicalGrade', + 'hiredate', 'age', 'phoneNumber', 'marital', 'politicsStatus', 'educationBackground', 'graduatedFrom', 'employmentLife', 'occupationalHistory']) ) : ""; @@ -203,38 +210,60 @@ const employeeInformation = (props) => { 职位 ), + width: 100, + dataIndex: "userJob", + key: "userJob", + render: (_, r, index) => { + return r.userJob ? UserAttribute.jobDataSource[r.userJob - 1] : '-'; + }, + }, { + title: ( +
+ 岗位 +
+ ), + width: 120, + dataIndex: "userPost", + key: "userPost", + render: (_, r, index) => { + return r.userPost || '-'; + }, + }, { + title: ( +
+ 在职状态 +
+ ), + width: 110, + dataIndex: "userActiveStatus", + key: "userActiveStatus", + render: (_, r, index) => { + return r.userActiveStatus ? UserAttribute.activeStatusDataSource[r.userActiveStatus - 1] : '-'; + }, + }, { + title: '绩点', + width: 100, + dataIndex: "point", + key: "point", + render: (_, r, index) => -, + }, { + title: ( +
+ 所属机构 +
+ ), width: 150, - dataIndex: "roleName", - key: "roleName", + dataIndex: "userOrganization", + key: "userOrganization", render: (_, r, index) => { - return ( -
- { - r.role.map((ite, idx) => { - let roleArr = [] - for (let i = 0; i < r.role.length; i++) { - roleArr.push(r.role[i].name) - } - return ( -
- {idx == 0 ? - (ite.name) : ('') - } - { - r.role.length > 1 && idx == 1 ? ( - -
- +{r.role.length - 1} -
-
- ) : ('') - } -
- ) - }) - } -
); + return r.userOrganization ? UserAttribute.organizationDataSource[r.userOrganization - 1] : '-'; }, + }, { + title: '技术职级等级', + width: 150, + dataIndex: "technicalGrade", + key: "technicalGrade", + render: (_, r, index) => -, }, { title: '证件号', width: 180, @@ -427,13 +456,13 @@ const employeeInformation = (props) => { }, }, ]; - for (let i = 0; i < arr.length; i++) { - let colum = column.filter((item) => { - return item.key === arr[i]; - }); - columns.splice(i + 2, 0, colum[0]); + let newColumns = columns; + for (let i = 0; i < column.length; i++) { + if (arr.indexOf(column[i].key) > -1) { + newColumns.push(column[i]); + } } - setSetupp(columns); + setSetupp(newColumns); } function handleRow(record, index) {//斑马条纹 // 给偶数行设置斑马纹 @@ -485,7 +514,7 @@ const employeeInformation = (props) => { style={{ width: 200 }} initValue={"name"} > - 职位 + 岗位 部门 编号 姓名 @@ -668,7 +697,7 @@ const employeeInformation = (props) => { { setSetup(false); attribute(); 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 4526bef..961db19 100644 --- a/web/client/src/sections/humanAffairs/containers/import-members-modal.js +++ b/web/client/src/sections/humanAffairs/containers/import-members-modal.js @@ -343,7 +343,7 @@ const ImportMembersModal = props => { error(`第${i + 2}行工作经验(年)错误,请填写非负数`) return } - postData.push({//人员编号 待办todotodo + postData.push({ pepUserId: xqExist.pepUserId, name, idNumber, gender, birthday, nativePlace, marital, politicsStatus, phoneNumber, workPlace, graduatedFrom, educationBackground, specialty, graduationDate, hiredate, turnProbationPeriod, regularDate, dimissionDate, experienceYear, occupationalHistory, diff --git a/web/client/src/sections/humanAffairs/containers/index.js b/web/client/src/sections/humanAffairs/containers/index.js index 58f7233..8a0f6c0 100644 --- a/web/client/src/sections/humanAffairs/containers/index.js +++ b/web/client/src/sections/humanAffairs/containers/index.js @@ -9,6 +9,7 @@ import LeaveStatistics from './leaveStatistics'; import OvertimeStatistics from './overtimeStatistics'; //招聘 import AppointmentRecords from './appointmentRecords'; +import PersonnelDistribution from './salersDistribution/personnelDistribution'; //培训 import ResourceRepository from './resourceRepository'; //绩效考核 @@ -32,7 +33,7 @@ import PersonnelFilesDetail from './personnelFilesDetail'; export { PersonnelFiles, EmployeeInformation, DeptArchives, AttendanceStatistics, LeaveStatistics, OvertimeStatistics, - AppointmentRecords, + AppointmentRecords, PersonnelDistribution, ResourceRepository, WeeklyManagement, SaleLog, PMLog, ProbationerKPI, RegularKPI, diff --git a/web/client/src/sections/humanAffairs/containers/personnelFiles.jsx b/web/client/src/sections/humanAffairs/containers/personnelFiles.jsx index d3420f4..7dd5c49 100644 --- a/web/client/src/sections/humanAffairs/containers/personnelFiles.jsx +++ b/web/client/src/sections/humanAffairs/containers/personnelFiles.jsx @@ -87,7 +87,7 @@ const Rest = (props) => {
+ 部门 + 地区 + +
+
+ } + showClear + placeholder='请输入关键词搜索' + value={keyword} + style={{ width: 346 }} + onChange={seachValueChange}> + +
+ +
+
+
{ setImportModalV(true); }}> + 导入 +
+ {/*
+ 导出 +
*/} +
+ + +
+ + 表格中带有认证标识" + + "信息的为系统基础数据,来源于项企PEP、钉钉等系统,其他数据均为导入或自定义数据 +
+ +
+ + { + if (sorter.key == 'userCode') { + if (sorter.sortOrder == 'descend') { + setOrder({ orderBy: 'code', orderDirection: 'DESC' }) + } else { + setOrder({ orderBy: 'code', orderDirection: 'ASC' }) + } + } else if (sorter.key == 'age') { + if (sorter.sortOrder == 'descend') { + setOrder({ orderBy: 'age', orderDirection: 'DESC' }) + } else { + setOrder({ orderBy: 'age', orderDirection: 'ASC' }) + } + } else { + if (sorter.sortOrder == 'descend') { + setOrder({ orderBy: 'hiredate', orderDirection: 'DESC' }) + } else { + setOrder({ orderBy: 'hiredate', orderDirection: 'ASC' }) + } + } + }} + onRow={handleRow} + scroll={scroll} + /> + +
+
+
+ + 共{limits}条信息 + + { + setQuery({ limit: pageSize, page: currentPage - 1 }); + page.current = currentPage - 1 + }} + /> +
+
+ + + + { + modalV ? closeAndFetch()} + onCancel={() => setModalV(false)} /> : '' + } + { + importModalV ? { + setImportModalV(false); + getMemberSearchList(); + }} /> : '' + } + ) +} + +function mapStateToProps(state) { + const { auth, global, SalesMemberList } = state; + return { + user: auth.user, + actions: global.actions, + salesMemberList: SalesMemberList.data + }; +} + +export default connect(mapStateToProps)(PersonnelDistribution); \ No newline at end of file diff --git a/web/client/src/sections/humanAffairs/containers/salersDistribution/salesMemberModal.js b/web/client/src/sections/humanAffairs/containers/salersDistribution/salesMemberModal.js new file mode 100644 index 0000000..b00f3a5 --- /dev/null +++ b/web/client/src/sections/humanAffairs/containers/salersDistribution/salesMemberModal.js @@ -0,0 +1,263 @@ +import React, { useEffect, useRef, useState } from 'react'; +import moment from 'moment'; +import { connect } from "react-redux"; +import { Select, Modal, Form, Notification } from "@douyinfe/semi-ui"; +import cityData from '../../components/city.json'; +const businessLines = ['市政', '地灾', '水利', '智慧城市', '工地', '环保', '安防', '产品投标', '交通', '矿山', '产品线'] +const SalesMemberModal = (props) => { + const { dispatch, actions, user, meetingList, onConfirm, getMultis, onCancel, close, rzMembers, dataToEdit } = props; + const { humanAffairs } = actions; + const form = useRef();//表单 + const [lineOptions, setLineOptions] = useState([]); + const [options, setOptions] = useState([]); + const [cityOptions, setCityOptions] = useState([]); + const [peoplePro, setPeoplePro] = useState({}); //人员信息 + //初始化 + useEffect(() => { + let optionItems = cityData.map(m => { + return + {m.name} + + }) + setOptions(optionItems); + + let lineOptions = businessLines.map((l, index) => { + return + {l} + + }) + setLineOptions(lineOptions); + + if (dataToEdit) { + setPeoplePro(dataToEdit); + onChange(dataToEdit.provinces?.split('、') || []);//市options + } + }, []); + + const onChange = (value) => { + let cityOptions = [], citiesRange = []; + cityData.filter(cd => value.indexOf(cd.name) !== -1).map(d => { + d.children?.map(c => { + cityOptions.push( + {c.name} + ) + citiesRange.push(c.name) + }) + }) + setCityOptions(cityOptions) + + let citiesValue = form?.current?.getValues()?.cities || []; + if (citiesValue.length) { + let newCities = []; + citiesValue?.map(cv => { + if (citiesRange.indexOf(cv) != -1) { + newCities.push(cv); + } + }) + form.current.setValue('cities', newCities) + } + } + + function handleOk() { + form.current.validate().then((values) => { + if (peoplePro == 'noValid') { + Notification.error({ + content: '你填写的员工编号无对应的人员信息', + duration: 2, + }) + } else if (peoplePro?.userCode) { + if (values.userCode == peoplePro.userCode) { + values.provinces = values.provinces.join('、') + values.cities = values.cities.join('、') + values.businessLines = values.businessLines.join('、') + if (dataToEdit) { + dispatch(humanAffairs.editSalesMember({ pepUserId: peoplePro.pepUserId, msg: '编辑销售人员信息', ...values })).then((res) => { + if (res.success) { + close(); + } + }) + } else { + dispatch(humanAffairs.postSalesMember({ pepUserId: peoplePro.pepUserId, msg: '新增销售人员', ...values })).then((res) => { + if (res.success) { + close(); + } + }) + } + } else { + Notification.error({ + content: '你填写的员工编号无对应的人员信息', + duration: 2, + }) + } + } else { + Notification.error({ + content: '请查询人员编号对应的员工信息', + duration: 2, + }) + } + }) + } + + const memberSeach = (id) => {//搜索项企用户 + // dispatch(humanAffairs.getMemberSearch({ code: id })).then((res) => {//搜索项企用户 + // if (res.success) { + // if (res.payload.data.length) { + // let user = res.payload.data[0] + let exist = rzMembers.find(m => m.userCode == id);//人员档案里面需要有 + if (exist) { + let item = { + pepUserId: exist.pepUserId, + name: exist.userName, + department: exist.departmrnt, + hireDate: exist.hiredate, + regularDate: exist.regularDate, + userCode: exist.userCode, + post: exist.userPost//岗位 + } + setPeoplePro(item) + } else { + setPeoplePro('noValid') + } + // } else { + // setPeoplePro('noValid') + // } + // } + // }) + } + + const renderSimpleInfo = () => { + let arrStr = peoplePro?.department?.map(t => t.name) || []; + return
+ { + peoplePro == 'noValid' ? +
+ 没有符合条件的结果 +
: + peoplePro?.name ? ( +
+
+ {renderPeopleItem('姓名:', peoplePro.name)} +
+
+ +
+
+ 所属部门: +
+ {getMultis(arrStr)} +
+
+
+ {renderPeopleItem('入职时间:', peoplePro.hireDate)} + {renderPeopleItem('岗位:', peoplePro.post)} +
+ {renderPeopleItem('转正时间:', peoplePro.regularDate)} + {renderPeopleItem('工龄:', peoplePro.hireDate ? String(moment(new Date()).diff(peoplePro.hireDate, 'years', true)).substring(0, 3) + '年' : '-')} +
+
+ ) : ('') + } +
+ } + + const renderPeopleItem = (label, value) => { + return
+
+ +
+
+ {label} +
+
+ {value || '-'} +
+
+ } + + const onClear = () => { + form.current.setValue('cities', []) + } + + return ( + +
(form.current = formApi)} + labelPosition={'left'} labelCol={{ span: 8 }} wrapperCol={{ span: 16 }}> + setPeoplePro({})} + addonAfter={
{ + let formList = form.current.getValues() + if (formList.userCode) { + memberSeach(formList.userCode) + } + }}> + 搜索 +
} /> + + {peoplePro ? renderSimpleInfo() : ''} + onClear()} + onChange={value => onChange(value)} + maxTagCount={5} + > + {options} + + + {cityOptions} + + + {lineOptions} + + +
+ ) +} + +function mapStateToProps(state) { + const { auth, global, MemberList } = state; + return { + user: auth.user, + actions: global.actions, + apiRoot: global.apiRoot, + rzMembers: MemberList.data?.rows || [], + }; +} + +export default connect(mapStateToProps)(SalesMemberModal); \ No newline at end of file diff --git a/web/client/src/sections/humanAffairs/nav-item.jsx b/web/client/src/sections/humanAffairs/nav-item.jsx index 3696ea1..a7b5010 100644 --- a/web/client/src/sections/humanAffairs/nav-item.jsx +++ b/web/client/src/sections/humanAffairs/nav-item.jsx @@ -46,6 +46,14 @@ export function getNavItem(user, dispatch) { items: [{ itemKey: 'appointmentRecords', to: '/humanAffairs/recruit/recruitRecord/appointmentRecords', text: '任用记录' }] + }, { + itemKey: 'salesStatistics', + text: '销售统计', + icon: ,//wwwtodo + to: '/humanAffairs/recruit/salesStatistics/personnelDistribution', + items: [{ + itemKey: 'personnelDistribution', to: '/humanAffairs/recruit/salesStatistics/personnelDistribution', text: '销售人员分布' + }] }, { itemKey: 'trainFiles', text: '培训档案', diff --git a/web/client/src/sections/humanAffairs/routes.js b/web/client/src/sections/humanAffairs/routes.js index 9815cf7..91ffb0c 100644 --- a/web/client/src/sections/humanAffairs/routes.js +++ b/web/client/src/sections/humanAffairs/routes.js @@ -2,7 +2,7 @@ import { PersonnelFiles, EmployeeInformation, //人员档案 DeptArchives, //部门档案 AttendanceStatistics, LeaveStatistics, OvertimeStatistics, - AppointmentRecords, + AppointmentRecords, PersonnelDistribution, ResourceRepository, WeeklyManagement, SaleLog, PMLog, ProbationerKPI, RegularKPI, @@ -89,6 +89,16 @@ export default [{ component: AppointmentRecords, breadcrumb: '任用记录', }] + }, { + path: '/salesStatistics', + key: 'salesStatistics', + breadcrumb: '销售统计', + childRoutes: [{ + path: '/personnelDistribution', + key: 'personnelDistribution', + component: PersonnelDistribution, + breadcrumb: '销售人员分布', + }] }] }, { path: '/train', diff --git a/web/client/src/utils/index.js b/web/client/src/utils/index.js index 0084428..860a315 100644 --- a/web/client/src/utils/index.js +++ b/web/client/src/utils/index.js @@ -6,7 +6,7 @@ import { AxyRequest, EmisRequest, basicAction, RouteRequest } from './webapi' - +import { UserAttribute } from './userAttribute'; export { isAuthorized, AuthorizationCode, @@ -17,4 +17,5 @@ export { EmisRequest, basicAction, RouteRequest, + UserAttribute } \ No newline at end of file diff --git a/web/client/src/utils/userAttribute.js b/web/client/src/utils/userAttribute.js new file mode 100644 index 0000000..2a28e82 --- /dev/null +++ b/web/client/src/utils/userAttribute.js @@ -0,0 +1,8 @@ +'use strict'; + +export const UserAttribute = { + jobDataSource: ['普通员工', '中层', '高层'], + activeStatusDataSource: ['在职', '离职', '特殊状态-特殊账号'], + organizationDataSource: ['江西飞尚科技有限公司', '江西飞尚工程质量检测有限公司', + '江西飞尚科技有限公司江苏分公司', '江西汇派科技有限公司'], +}; diff --git a/web/client/src/utils/webapi.js b/web/client/src/utils/webapi.js index ccfc4d2..b2b85cb 100644 --- a/web/client/src/utils/webapi.js +++ b/web/client/src/utils/webapi.js @@ -31,6 +31,12 @@ export const ApiTable = { getAttendanceVacate: 'attendance/vacate',//请假统计 getAttendanceVacateType: 'attendance/vacate/type',//请假类型 getAttendanceOvertime: 'attendance/overtime',//加班统计 + + getSalesList: 'sales/member/list', + addSalesMember: 'sales/member/add', + editSalesMember: 'sales/member/modify', + delSalesMember: 'sales/member/del', + addSalesMemberBulk: 'add/sales/members/bulk' }; export const RouteTable = { apiRoot: "/api/root",