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) => {
- {
- item.role.map((ite, idx) => {
- let roleArr = []
- for (let i = 0; i < item.role.length; i++) {
- roleArr.push(item.role[i].name)
- }
- return (
-
- {idx == 0 ?
- (ite.name.substring(0, 6)) : ('')
- }
- {
- item.role.length > 1 && idx == 1 ? (
-
-
- +{item.role.length - 1}
-
-
- ) : ('')
- }
-
- )
- })
- }
+ {item.userPost || '暂无'}
diff --git a/web/client/src/sections/humanAffairs/containers/personnelFilesDetail.jsx b/web/client/src/sections/humanAffairs/containers/personnelFilesDetail.jsx
index 46a4e8e..c2b889b 100644
--- a/web/client/src/sections/humanAffairs/containers/personnelFilesDetail.jsx
+++ b/web/client/src/sections/humanAffairs/containers/personnelFilesDetail.jsx
@@ -7,6 +7,7 @@ import * as echarts from 'echarts';
import DeleteModal from '../components/deleteModal';
import PersonnelModal from '../components/personnelModal';
import moment from 'moment'
+import { UserAttribute } from '$utils'
import '../style.less'
@@ -610,7 +611,7 @@ const Rest = (props) => {
职务信息
-
+
员工编号:
@@ -618,7 +619,7 @@ const Rest = (props) => {
{pepObj.userCode || '暂无'}
-
+
入职时间:
@@ -626,7 +627,7 @@ const Rest = (props) => {
{pepObj.hiredate || '暂无'}
-
+
转试用期时间:
@@ -644,43 +645,38 @@ const Rest = (props) => {
-
+
员工职位:
-
+
+ {
+ pepObj.userJob? UserAttribute.jobDataSource[pepObj.userJob - 1] : '暂无'
+ }
+
+
+
+
+ 员工岗位:
+
+
{
- pepObj?.role?.map((item, index) => {
- let roleArr = []
- for (let i = 0; i < pepObj.role.length; i++) {
- roleArr.push(pepObj.role[i].name)
- }
- return (
-
- {index < 2 ?
- (item.name) : ('')
- }
- {
- pepObj.role.length > 1 && index == 0 ?
- (',') : ('')
- }
- {
- pepObj.role.length > 2 && index == 2 ? (
-
-
- +{pepObj.role.length - 2}
-
-
- ) : ('')
- }
-
- )
- })
+ pepObj.userPost || '暂无'
}
-
+
+ 所属机构:
+
+
+ {
+ pepObj.userOrganization? UserAttribute.organizationDataSource[pepObj.userOrganization - 1] : '暂无'
+ }
+
+
+
+
所属部门:
{
@@ -712,7 +708,7 @@ const Rest = (props) => {
-
+
工作经验:
@@ -720,7 +716,7 @@ const Rest = (props) => {
{pepObj.experienceYear ? pepObj.experienceYear + '年' : '暂无'}
-
+
入职年限:
@@ -728,7 +724,7 @@ const Rest = (props) => {
{pepObj.hiredate ? String(moment(new Date()).diff(pepObj.hiredate, 'years',true)).substring(0,3) + '年' : '暂无'}
-
+
试用期:
diff --git a/web/client/src/sections/humanAffairs/containers/salersDistribution/importSalersModal.js b/web/client/src/sections/humanAffairs/containers/salersDistribution/importSalersModal.js
new file mode 100644
index 0000000..a10cdaa
--- /dev/null
+++ b/web/client/src/sections/humanAffairs/containers/salersDistribution/importSalersModal.js
@@ -0,0 +1,272 @@
+'use strict';
+import React, { useState, useEffect } from 'react';
+import { connect } from 'react-redux';
+import { Modal, Form, Button, Notification } from '@douyinfe/semi-ui';
+import { IconUpload } from '@douyinfe/semi-icons';
+import cityData from '../../components/city.json';
+import XLSX from 'xlsx'
+//下载模板和上传文件读取
+const ImportSalersModal = props => {
+ const { dispatch, actions, onCancel, rzMembers } = props
+ const { humanAffairs } = actions;
+ const [msg, setMsg] = useState('')
+ const [loading, setLoading] = useState('')
+ const [postData, setPostData] = useState([])
+ const [allProvinces, setAllProvinces] = useState([])
+ //初始化
+ useEffect(() => {
+ let allProvinces = [];//所有省
+ cityData.map(cd => {
+ allProvinces.push(cd.name);
+ });
+ setAllProvinces(allProvinces);
+ }, []);
+
+ const confirm = () => {
+ if (postData.length) {
+ setLoading(true)
+ dispatch(humanAffairs.addSalesMemberBulk(postData)).then(res => {
+ if (res.success) {
+ onCancel()
+ }
+ setLoading(false)
+ })
+ } else {
+ Notification.warning({ content: '没有数据可以提交,请上传数据文件', duration: 2 })
+ }
+ }
+
+ const dldCsvMb = () => {
+ //表头
+ let head = "员工编号,姓名,销售区域(省/直辖市),销售区域(市),业务线\n"
+ //数据
+ //let data = 1 + ',' + 2 + ',' + 3 + ',' + 4 + ',' + 5
+ let templateCsv = "data:text/csv;charset=utf-8,\ufeff" + head;
+ //创建一个a标签
+ let link = document.createElement("a");
+ //为a标签设置属性
+ link.setAttribute("href", templateCsv);
+ link.setAttribute("download", "人资系统销售人员信息导入模板.csv");
+ //点击a标签
+ link.click();
+ }
+ const download = () => {
+ //dldTemplate();
+ dldCsvMb();
+ // let str = "";
+ // rule.forEach((v, i) => {
+ // str += `${v}\r\n`
+ // })
+ // dldText("填写说明.txt", str);
+ }
+
+ // const dldText = (filename, text) => {
+ // var element = document.createElement('a');
+ // element.setAttribute('href', 'data:text/plain;charset=utf-8,' + encodeURIComponent(text));
+ // element.setAttribute('download', filename);
+
+ // element.style.display = 'none';
+ // document.body.appendChild(element);
+
+ // element.click();
+ // document.body.removeChild(element);
+ // }
+ const fileLimit = '.csv';
+
+ const getFileBlob = (url) => {
+ return new Promise((resolve, reject) => {
+ let request = new XMLHttpRequest()
+ request.open("GET", url, true)
+ request.responseType = "blob"
+ request.onreadystatechange = e => {
+ if (request.readyState == 4) {
+ if (request.status == 200) {
+ if (window.FileReader) {
+ let reader = new FileReader();
+ reader.readAsBinaryString(request.response);
+ reader.onload = event => {
+ try {
+ const { result } = event.target;
+ // 以二进制流方式读取得到整份excel表格对象
+ const workbook = XLSX.read(result, {
+ type: "binary",
+ cellDates: true,//设为true,将天数的时间戳转为时间格式
+ codepage: 936//解决了乱码问题
+ });
+ let data = []; // 存储获取到的数据
+ // 遍历每张工作表进行读取(这里默认只读取第一张表)
+ for (const sheet in workbook.Sheets) {
+ if (workbook.Sheets.hasOwnProperty(sheet)) {
+ data = data.concat(XLSX.utils.sheet_to_json(workbook.Sheets[sheet]));
+ }
+ }
+ resolve(data);//导出数据
+ } catch (e) {
+ reject("失败");
+ }
+ }
+ }
+ }
+ }
+ }
+ request.send();
+ })
+ }
+
+ const judgeProvinces = (provinces) => {
+ let noMark = 0;
+ provinces?.split('、')?.map(p => {
+ if (allProvinces.indexOf(p) == -1) {
+ noMark++
+ }
+ })
+ return !noMark;
+ }
+
+ const judgeCities = (provinces, cities) => {
+ if (!cities) {//可以不填
+ return true;
+ }
+ let nowCities = [];
+ cityData.filter(cd => provinces?.split('、').indexOf(cd.name) != -1).map(d => {
+ d.children?.map(c => {
+ nowCities.push(c.name)
+ })
+ })
+ let noMark = 0;
+ cities?.split('、')?.map(p => {
+ if (nowCities.indexOf(p) == -1) {
+ noMark++
+ }
+ })
+ return !noMark;
+ }
+
+ const judgeNull = (value) => {
+ return value ? String(value).trim().replace(/\s*/g, "") : null;
+ }
+
+ return (
+
{
+ setMsg('')
+ setLoading(false)
+ setPostData([])
+ onCancel()
+ }}
+ >
+
+ {
+ setMsg('');
+ setPostData([]);
+ }}
+ customRequest={(data) => {
+ const { file, onSuccess, onError } = data
+ getFileBlob(file.url).then(res => {
+ const error = (msg) => {
+ setMsg(msg)
+ onError({ message: msg })
+ }
+ if (res.length > 1000) {
+ error('一次性上传数据行数应小于1000行,请分批上传')
+ return
+ }
+ if (!res.length) {
+ error('请填写至少一行数据')
+ return
+ }
+ let postData = [];
+ const zmsz = /^[A-Za-z0-9]+$/;//字母数字组合
+ for (let i = 0; i < res.length; i++) {
+ let d = res[i];
+ let number = judgeNull(d['员工编号']);
+ let name = judgeNull(d['姓名']);
+ let provinces = judgeNull(d['销售区域(省/直辖市)']);
+ let cities = judgeNull(d['销售区域(市)']);
+ let businessLines = judgeNull(d['业务线']);
+ if (!number) {//人员编号不为空,唯一,字母和数字
+ error(`第${i + 2}行人员编号为空,请填写`)
+ return
+ }
+ let rzExist = rzMembers.find(m => m.userCode == number);
+ if (!rzExist) {
+ error(`第${i + 2}行的人员编号无对应的员工信息`)
+ return
+ }
+ if (postData.some(p => p.number == number)) {//人员编号 唯一
+ error(`第${i + 2}行人员编号重复,请更改后重新上传`)
+ return
+ }
+ if (!zmsz.test(number)) {
+ error(`第${i + 2}行人员编号错误,请填写字母和数字的组合`)
+ return
+ }
+ if (!name) {//姓名必填
+ error(`第${i + 2}行姓名为空,请填写`)
+ return
+ }
+ if (!provinces) {//销售区域(省/直辖市)必填
+ error(`第${i + 2}行销售区域(省/直辖市)为空,请填写`)
+ return
+ }
+ let pValid = judgeProvinces(provinces);
+ if (!pValid) {
+ error(`第${i + 2}行销售区域(省/直辖市)错误`)
+ return
+ }
+ let cValid = judgeCities(provinces, cities);
+ if (!cValid) {
+ error(`第${i + 2}行销售区域(市)错误`)
+ return
+ }
+ //todo 业务线判断
+ postData.push({
+ pepUserId: rzExist.pepUserId, name, number,
+ provinces: provinces || '', cities: cities || '', businessLines: businessLines || '',
+ del: false
+ })
+ }
+ setPostData(postData)
+ let msg = '文件解析完成,点击确定按钮上传保存!'
+ setMsg(msg)
+ onSuccess({ message: msg })
+ })
+ }}>
+ } theme="light">
+ 请选择文件
+
+
+ {msg}
+ 最大不超过200M,导入文件需与
+ download()} style={{ cursor: 'pointer', color: '#0066FF' }}>导入模板
+ 一致
+
+
填写要求:
+
员工编号:必填,唯一,数字和字母的组合;
+
姓名:必填,若与员工编号对应的项企用户姓名不同,将以项企数据为准;
+
销售区域(省/直辖市):必填,省或直辖市顿号隔开,如“北京市、江西省、江苏省”;
+
销售区域(市):非必填,归属所填省的地级市顿号隔开,如“南昌市、镇江市”。
+
+
+
+ )
+}
+
+function mapStateToProps(state) {
+ const { auth, global, MemberList } = state;
+ return {
+ user: auth.user,
+ actions: global.actions,
+ rzMembers: MemberList.data?.rows || [],
+ }
+}
+
+export default connect(mapStateToProps)(ImportSalersModal);
\ No newline at end of file
diff --git a/web/client/src/sections/humanAffairs/containers/salersDistribution/personnelDistribution.jsx b/web/client/src/sections/humanAffairs/containers/salersDistribution/personnelDistribution.jsx
new file mode 100644
index 0000000..b472776
--- /dev/null
+++ b/web/client/src/sections/humanAffairs/containers/salersDistribution/personnelDistribution.jsx
@@ -0,0 +1,350 @@
+import React, { useEffect, useRef, useState, useMemo } from 'react';
+import { connect } from 'react-redux';
+import moment from 'moment'
+import { Select, Input, Button, Popconfirm, Radio, Tooltip, Table, Pagination, Skeleton } from '@douyinfe/semi-ui';
+import SalesMemberModal from './salesMemberModal'
+import ImportSalersModal from './importSalersModal'
+import { IconSearch } from '@douyinfe/semi-icons';
+import { SkeletonScreen } from "$components";
+import '../../style.less'
+
+const PersonnelDistribution = (props) => {
+ const { dispatch, actions } = props
+ const { humanAffairs } = actions;
+ const [keywordTarget, setKeywordTarget] = useState('dep');
+ const [keyword, setKeyword] = useState('');//搜索内容
+ const [limits, setLimits] = useState()//每页实际条数
+ const [query, setQuery] = useState({ limit: 10, page: 0 }); //页码信息
+ const [modalV, setModalV] = useState(false);
+ const [dataToEdit, setDataToEdit] = useState(null);
+ const [tableData, setTableData] = useState([]);
+ const [importModalV, setImportModalV] = useState(false);
+ const page = useRef(query.page);
+ function seachValueChange(value) {
+ setKeyword(value)
+ }
+
+ useEffect(() => {
+ dispatch(humanAffairs.getMemberList())
+ getMemberSearchList()
+ }, []);
+
+ useEffect(() => {
+ getMemberSearchList()//查询人员列表
+ }, [query])
+
+ function getMemberSearchList() {
+ let kt = keywordTarget == 'place' ? '' : keywordTarget;
+ let k = keywordTarget == 'place' ? '' : keyword;
+ let placeSearch = keywordTarget == 'place' ? keyword : '';
+ dispatch(humanAffairs.getSalesList({ keywordTarget: kt, keyword: k, placeSearch, ...query })).then(r => {
+ if (r.success) {
+ setTableData(r.payload?.data?.rows);
+ setLimits(r.payload?.data?.count)
+ }
+ })
+ }
+
+ function handleRow(record, index) {// 给偶数行设置斑马纹
+ if (index % 2 === 0) {
+ return {
+ style: {
+ background: '#FAFCFF',
+ }
+ };
+ } else {
+ return {};
+ }
+ }
+
+ const closeAndFetch = () => {
+ setModalV(false)
+ getMemberSearchList();
+ }
+
+ const starHeader = (header) => {
+ return
+

{header}
+
+ }
+
+ const getMultis = (arrStr) => {//默认展示2个
+ return
+ {
+ arrStr.length ?
+ arrStr.map((ite, idx) => {
+ return (
+
+ {idx < 2 ?
+
+ {ite}
+
: ''
+ }
+ {
+ arrStr.length > 2 && idx == 2 ?
+
+
+ +{arrStr.length - 2}
+
+
+ : ''
+ }
+
+ )
+ }) : '-'
+ }
+
+ }
+
+ const columns = [{
+ title: '序号',
+ dataIndex: 'id',
+ key: 'id',
+ width: 60,
+ render: (text, record, index) => index + 1
+ }, {
+ title: starHeader('姓名'),
+ dataIndex: 'name',
+ key: 'name',
+ width: 80
+ },
+ {
+ title: starHeader('部门名称'),
+ dataIndex: 'department',
+ key: 'department',
+ width: 200,
+ render: (text, r, index) => {
+ let arrStr = text.map(t => t.name);
+ return getMultis(arrStr);
+ }
+ }, {
+ title: '销售区域(省/直辖市)',
+ dataIndex: 'provinces',
+ key: 'provinces',
+ width: 160,
+ render: (text, record, index) => {
+ return getMultis(text?.split('、') || []);
+ }
+ }, {
+ title: '销售区域(市)',
+ dataIndex: 'cities',
+ key: 'cities',
+ width: 160,
+ render: (text, record, index) => {
+ return text ? getMultis(text?.split('、') || []) : '-';
+ }
+ }, {
+ title: '业务线',
+ dataIndex: 'businessLines',
+ key: 'businessLines',
+ width: 120,
+ render: (text, record, index) => {
+ return text ? getMultis(text?.split('、') || []) : '-';
+ }
+ }, {
+ title: starHeader('岗位'),
+ dataIndex: 'post',
+ key: 'post',
+ width: 120,
+ render: (text, record) =>
{text || '-'}
+ }, {
+ title: starHeader('入职时间'),
+ dataIndex: 'hireDate',
+ key: 'hireDate',
+ width: 120,
+ render: (text, record) =>
{text || '-'}
+ }, {
+ title: starHeader('转正时间'),
+ dataIndex: 'regularDate',
+ key: 'regularDate',
+ width: 120,
+ render: (text, record) =>
{text || '-'}
+ }, {
+ title: starHeader('工龄'),
+ dataIndex: 'workYears',
+ key: 'workYears',
+ width: 120,
+ render: (_, r, index) => {
+ return (r.hireDate ?
{String(moment(new Date()).diff(r.hireDate, 'years', true)).substring(0, 3) + '年'} : '-')
+ },
+ }, {
+ title: '操作',
+ dataIndex: 'action',
+ width: 120,
+ render: (text, record) => {
+ return
+
onEdit(record)}>编辑
+
confirmDelete(record.pepUserId)} style={{ width: 330 }}
+ > 删除
+
+ }
+ }];
+
+ const onEdit = (data) => {
+ setModalV(true);
+ setDataToEdit(data);
+ }
+
+ const confirmDelete = (pepUserId) => {
+ dispatch(humanAffairs.delSalesMember({ pepUserId, msg: '删除销售人员信息' })).then(res => {
+ if (res.success) {
+ getMemberSearchList();
+ }
+ });
+ }
+ const scroll = useMemo(() => ({}), []);
+ return (
+
+
招聘
+
/
+
销售统计
+
/
+
销售人员分布
+
+
+
+
+
+
销售人员分布
+
DISTRIBUTION OF SALES PERSONNEL
+
+
+
+
+
+
{
+ setModalV(true);
+ setDataToEdit(null);
+ }}>
+ 新增
+
+
+
+
+
+ }
+ 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 (
+
+ 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",