Browse Source

(+)权限控制

master
ww664853070 1 year ago
parent
commit
346b9ecdee
  1. 14
      api/.vscode/launch.json
  2. 42
      api/app/lib/controllers/attendance/index.js
  3. 172
      api/app/lib/controllers/auth/index.js
  4. 1316
      api/app/lib/controllers/member/index.js
  5. 4
      api/app/lib/controllers/salesDistribution/index.js
  6. 20
      api/app/lib/middlewares/authenticator.js
  7. 70
      api/app/lib/models/resource.js
  8. 82
      api/app/lib/models/role.js
  9. 43
      api/app/lib/models/role_resource.js
  10. 75
      api/app/lib/models/user_role.js
  11. 43
      api/app/lib/models/vacate_remark.js
  12. 3
      api/app/lib/routes/attendance/index.js
  13. 242
      api/app/lib/schedule/attendance.js
  14. 398
      api/app/lib/utils/member.js
  15. 2
      doc/scripts/3.11.0/add_primary_key.sql
  16. 23
      doc/scripts/3.11.0/create_vacate_remark.sql
  17. 19
      doc/scripts/tools/v3.22.1_init_HRM_resource/.vscode/launch.json
  18. 1
      doc/scripts/tools/v3.22.1_init_HRM_resource/Answer.txt
  19. 0
      doc/scripts/tools/v3.22.1_init_HRM_resource/AnswerOld.txt
  20. 62
      doc/scripts/tools/v3.22.1_init_HRM_resource/AuthCode.txt
  21. 12
      doc/scripts/tools/v3.22.1_init_HRM_resource/Dockerfile
  22. 24
      doc/scripts/tools/v3.22.1_init_HRM_resource/config.js
  23. 425
      doc/scripts/tools/v3.22.1_init_HRM_resource/index.js
  24. 19
      doc/scripts/tools/v3.22.1_init_HRM_resource/jenkinsfile
  25. 19
      doc/scripts/tools/v3.22.1_init_HRM_resource/jenkinsfile-registry
  26. 21
      doc/scripts/tools/v3.22.1_init_HRM_resource/package.json
  27. 39
      doc/scripts/tools/v3.22.1_init_HRM_resource/readme.txt
  28. 24
      web/client/src/components/setup.jsx
  29. 4
      web/client/src/sections/humanAffairs/actions/index.js
  30. 15
      web/client/src/sections/humanAffairs/actions/vacateRemark.js
  31. 78
      web/client/src/sections/humanAffairs/containers/leaveStatistics.jsx
  32. 18
      web/client/src/sections/humanAffairs/containers/salersDistribution/personnelDistribution.jsx
  33. 48
      web/client/src/sections/humanAffairs/containers/vacateRemark.js
  34. 2
      web/client/src/utils/webapi.js

14
api/.vscode/launch.json

@ -17,16 +17,16 @@
"-f http://localhost:4700", "-f http://localhost:4700",
// //
"-g postgres://postgres:123@10.8.30.166:5432/hr-dev", "-g postgres://postgres:123@10.8.30.166:5432/hr-dev",
"--redisHost 10.8.30.112", "--redisHost localhost",
"--redisPort 6379", "--redisPort 6379",
"--apiEmisUrl http://10.8.30.112:14000", "--apiEmisUrl http://10.8.30.103:14000",
// //
// "--apiEmisUrl http://10.8.30.161:1111", // "--apiEmisUrl http://10.8.30.161:1111",
"--qnak XuDgkao6cL0HidoMAPnA5OB10Mc_Ew08mpIfRJK5", "--qnak 5XrM4wEB9YU6RQwT64sPzzE6cYFKZgssdP5Kj3uu",
"--qnsk yewcieZLzKZuDfig0wLZ9if9jKp2P_1jd3CMJPSa", "--qnsk w6j2ixR_i-aelc6I7S3HotKIX-ukMzcKmDfH6-M5",
"--qnbkt dev-hr", "--qnbkt anxinyun-test",
// "--qndmn http://resources.anxinyun.cn", "--qndmn http://resources.anxinyun.cn",
"--qndmn http://rjkwed13l.hn-bkt.clouddn.com", // "--qndmn http://rjkwed13l.hn-bkt.clouddn.com",
// * 2 // * 2
// "--clickHouseUser ", // "--clickHouseUser ",

42
api/app/lib/controllers/attendance/index.js

@ -205,10 +205,14 @@ async function vacateStatistic(ctx) {
startDate, endDate, pepUserIds startDate, endDate, pepUserIds
}) })
const remarkList = await models.VacateRemark.findAll({}); //查询备注
returnD.forEach(u => { returnD.forEach(u => {
let vacateStatistic = sumRes.filter(s => s.pepUserId == u.pepUserId) let vacateStatistic = sumRes.filter(s => s.pepUserId == u.pepUserId)
let remarkData = remarkList.filter(e => e.pepUserId == u.pepUserId)
u.vacateDuration = vacateStatistic.reduce((sum, vs) => sum + vs.duration, 0) u.vacateDuration = vacateStatistic.reduce((sum, vs) => sum + vs.duration, 0)
u.vacateStatistic = vacateStatistic u.vacateStatistic = vacateStatistic
u.remark = remarkData.length ? remarkData[0].remark : null
}) })
ctx.status = 200; ctx.status = 200;
ctx.body = { ctx.body = {
@ -255,6 +259,8 @@ async function exportVacateStatistic(ctx) {
startDate, endDate, pepUserIds startDate, endDate, pepUserIds
}) })
const remarkList = await models.VacateRemark.findAll({}); //查询备注
returnD.forEach(u => { returnD.forEach(u => {
u.departmrnt = u.departmrnt.map(dep => dep.name).join('、') u.departmrnt = u.departmrnt.map(dep => dep.name).join('、')
u.role = u.role.map(r => r.name).join('、') u.role = u.role.map(r => r.name).join('、')
@ -269,6 +275,9 @@ async function exportVacateStatistic(ctx) {
}, 0) }, 0)
} }
u.vacateDayStatisticDuration = (u.vacateDayStatisticDuration || 0) / 3600 u.vacateDayStatisticDuration = (u.vacateDayStatisticDuration || 0) / 3600
u.userActiveStatus = u.userActiveStatus == 1 ? '在职' : u.userActiveStatus == 2 ? '离职' : '特殊状态-特殊账号'
let remarkData = remarkList.filter(e => e.pepUserId == u.pepUserId)
u.remark = remarkData.length ? (remarkData[0].remark == '' ? '无' : remarkData[0].remark) : '无'
}) })
const header = [{ const header = [{
@ -283,6 +292,9 @@ async function exportVacateStatistic(ctx) {
}, { }, {
title: '职位', title: '职位',
key: 'role', key: 'role',
}, {
title: '在职状态',
key: 'userActiveStatus',
},] },]
.concat(vacateTypeRes.map(v => { .concat(vacateTypeRes.map(v => {
return { return {
@ -302,6 +314,12 @@ async function exportVacateStatistic(ctx) {
defaultValue: '0', defaultValue: '0',
}] }]
) )
.concat(
[{
title: '备注',
key: 'remark',
}]
)
const fileName = `请假统计_${startDate ? moment(startDate).format('YYYY-MM-DD') : ''}${startDate && endDate ? '-' : ''}${endDate ? moment(endDate).format('YYYY-MM-DD') : ''}${startDate || endDate ? '_' : ''}${moment().format('YYYYMMDDHHmmss')}` + '.csv' const fileName = `请假统计_${startDate ? moment(startDate).format('YYYY-MM-DD') : ''}${startDate && endDate ? '-' : ''}${endDate ? moment(endDate).format('YYYY-MM-DD') : ''}${startDate || endDate ? '_' : ''}${moment().format('YYYYMMDDHHmmss')}` + '.csv'
const filePath = await simpleExcelDown({ data: returnD, header, fileName: fileName }) const filePath = await simpleExcelDown({ data: returnD, header, fileName: fileName })
@ -320,10 +338,34 @@ async function exportVacateStatistic(ctx) {
} }
} }
// 添加请假统计备注
async function vacateRemark(ctx) {
try {
const { models } = ctx.fs.dc;
const { remark, pepUserId } = ctx.query;
let oldData = await models.VacateRemark.findOne({ where: { pepUserId: pepUserId } });
if (oldData) {
await models.VacateRemark.update({ remark, pepUserId }, { where: { pepUserId: pepUserId } });
} else {
await models.VacateRemark.create({ remark, pepUserId });
}
ctx.status = 200;
ctx.body = '添加备注成功';
} catch (error) {
ctx.fs.logger.error(`path: ${ctx.path}, error: ${error}`);
ctx.status = 400;
ctx.body = {
"message": "添加备注失败"
}
}
}
module.exports = { module.exports = {
overtimeStatistic, overtimeStatistic,
exportOvertimeStatistic, exportOvertimeStatistic,
vacateType, vacateType,
vacateStatistic, vacateStatistic,
exportVacateStatistic, exportVacateStatistic,
vacateRemark
}; };

172
api/app/lib/controllers/auth/index.js

@ -4,81 +4,127 @@ const MD5 = require('crypto-js/md5');
const moment = require('moment'); const moment = require('moment');
const uuid = require('uuid'); const uuid = require('uuid');
async function login (ctx, next) { async function getDataRange(ctx) {
// const transaction = await ctx.fs.dc.orm.transaction(); //null为所有权限,不加条件过滤
try { let userIds = null;
const models = ctx.fs.dc.models; let departmentNames = null;
const params = ctx.request.body; let userNames = null;
let dataRange = null;
let emisLoginRes = null const { adminHr, id, department, allDepartment } = ctx.fs.api.userInfo;
if (params.username && params.password) { //人资管理-所有权限
emisLoginRes = await ctx.app.fs.emisRequest.post('login', { if (adminHr.filter(admin => admin.id == id).length) {
data: { ...params, code: 'HR' } dataRange = 1;
}) } else {
} else if (params.token) { const models = ctx.fs.dc.models;
emisLoginRes = await ctx.app.fs.emisRequest.get('user-info', { //获取用户对应角色的最高数据范围
query: { let roleRes = await models.Role.findAll({
token: params.token, code: 'HR' include: [{
model: models.UserRole,
where: { userId: id }
}]
})
if (roleRes.some(r => r.dataRange && r.dataRange === 1)) {
dataRange = 1;
} else {
const departments = allDepartment.departments;
dataRange = 2;
userIds = [];
userNames = [];
if (department && departments) {
let userDeps = department.map(d => d.id);
departmentNames = department.map(d => d.name);
let deps = departments.filter(d => userDeps.indexOf(d.id) > -1)
if (deps && deps.length > 0) {
deps.map(d => d.users.map(u => {
if (userIds.indexOf(u.id) < 0) {
userIds.push(u.id);
userNames.push(u.name);
}
}));
}
} }
}) }
} }
return { dataRange, userIds, departmentNames, userNames };
}
async function login(ctx, next) {
// const transaction = await ctx.fs.dc.orm.transaction();
try {
const models = ctx.fs.dc.models;
const params = ctx.request.body;
let emisLoginRes = null
if (params.username && params.password) {
emisLoginRes = await ctx.app.fs.emisRequest.post('login', {
data: { ...params, code: 'HR' }
})
} else if (params.token) {
emisLoginRes = await ctx.app.fs.emisRequest.get('user-info', {
query: {
token: params.token, code: 'HR'
}
})
}
if (!emisLoginRes) { if (!emisLoginRes) {
throw "无此用户,请使用正确的登录信息" throw "无此用户,请使用正确的登录信息"
} else { } else {
emisLoginRes.authorized = true emisLoginRes.authorized = true
emisLoginRes.expired = moment().add(1, 'day') emisLoginRes.expired = moment().add(1, 'day')
emisLoginRes.hrUserInfo = undefined emisLoginRes.hrUserInfo = undefined
await ctx.redis.hmset(emisLoginRes.token, { await ctx.redis.hmset(emisLoginRes.token, {
expired: moment().add(1, 'day'), expired: moment().add(1, 'day'),
userInfo: JSON.stringify(emisLoginRes) userInfo: JSON.stringify(emisLoginRes)
}); });
ctx.status = 200; ctx.status = 200;
ctx.body = emisLoginRes; ctx.body = emisLoginRes;
} }
// await transaction.commit(); // await transaction.commit();
} catch (error) { } catch (error) {
// await transaction.rollback(); // await transaction.rollback();
ctx.fs.logger.error(`path: ${ctx.path}, error: ${error}`); ctx.fs.logger.error(`path: ${ctx.path}, error: ${error}`);
ctx.status = 400; ctx.status = 400;
let message = typeof error == 'string' ? error let message = typeof error == 'string' ? error
: error.response && error.response.body && error.response.body.message ? : error.response && error.response.body && error.response.body.message ?
error.response.body.message error.response.body.message
: "登录失败" : "登录失败"
if (message == '账号或密码错误') { if (message == '账号或密码错误') {
message = '无此用户,请使用正确的登录信息' message = '无此用户,请使用正确的登录信息'
} }
ctx.body = { ctx.body = {
message: message message: message
} }
} }
} }
async function logout (ctx) { async function logout(ctx) {
try { try {
const models = ctx.fs.dc.models; const models = ctx.fs.dc.models;
const params = ctx.request.body; const params = ctx.request.body;
await ctx.app.fs.emisRequest.put('logout', { await ctx.app.fs.emisRequest.put('logout', {
data: params data: params
}) })
await ctx.redisTools.hdelall(token); await ctx.redisTools.hdelall(token);
ctx.status = 204; ctx.status = 204;
} catch (error) { } catch (error) {
ctx.fs.logger.error(`path: ${ctx.path}, error: ${error}`); ctx.fs.logger.error(`path: ${ctx.path}, error: ${error}`);
ctx.status = 400; ctx.status = 400;
ctx.body = { ctx.body = {
} }
} }
} }
module.exports = { module.exports = {
login, login,
logout, logout,
getDataRange
}; };

1316
api/app/lib/controllers/member/index.js

File diff suppressed because it is too large

4
api/app/lib/controllers/salesDistribution/index.js

@ -8,7 +8,7 @@ async function salesList(ctx) {
const { const {
keywordTarget, keyword, limit, page, state, keywordTarget, keyword, limit, page, state,
hiredateStart, hiredateEnd, marital, native, workPlace, hiredateStart, hiredateEnd, marital, native, workPlace,
orderBy, orderDirection, placeSearch orderBy, orderDirection, placeSearch,userActiveStatus
} = ctx.query } = ctx.query
const userRes = await memberList({ const userRes = await memberList({
@ -55,10 +55,12 @@ async function salesList(ctx) {
department: info.departmrnt, department: info.departmrnt,
hireDate: info.hiredate,//入职时间 hireDate: info.hiredate,//入职时间
regularDate: info.regularDate,//转正时间 regularDate: info.regularDate,//转正时间
userActiveStatus:info.userActiveStatus,
...d.dataValues ...d.dataValues
} }
rslt.push(item); rslt.push(item);
}) })
rslt= rslt.filter(e=>e.userActiveStatus == userActiveStatus)
ctx.status = 200; ctx.status = 200;
ctx.body = { ctx.body = {
count: res.count, count: res.count,

20
api/app/lib/middlewares/authenticator.js

@ -72,32 +72,16 @@ let authorizeToken = async function (ctx, token) {
if (token && tokenFormatRegexp.test(token)) { if (token && tokenFormatRegexp.test(token)) {
try { try {
const expired = await ctx.redis.hget(token, 'expired'); const expired = await ctx.redis.hget(token, 'expired');
// const authorizeRes = await ctx.app.fs.emisRequest.get('authorize', {
// query: { token }
// })
// const { userInfo, expired } = authorizeRes;
// TODO 从项企 clickhouse 数据库中查 token 并更新 // TODO 从项企 clickhouse 数据库中查 token 并更新
if (expired && moment().valueOf() <= moment(expired).valueOf()) { if (expired && moment().valueOf() <= moment(expired).valueOf()) {
const userInfo = JSON.parse(await ctx.redis.hmget(token, 'userInfo')); const userInfo = JSON.parse(await ctx.redis.hmget(token, 'userInfo'));
const { pomsUserInfo: pomsUser } = userInfo
// const pomsUser = await ctx.app.fs.dc.models.User.findOne({
// where: {
// pepUserId: userInfo.id
// }
// }) || {}
rslt = { rslt = {
'authorized': userInfo.authorized, 'authorized': userInfo.authorized,
'resources': (userInfo || {}).resources || [], 'resources': (userInfo || {}).resources || [],
}; };
ctx.fs.api.userId = pomsUser.id; ctx.fs.api.userId = userInfo.id;
ctx.fs.api.userInfo = pomsUser; ctx.fs.api.userInfo = userInfo;
ctx.fs.api.pepUserId = userInfo.id; ctx.fs.api.pepUserId = userInfo.id;
ctx.fs.api.pepUserInfo = userInfo;
ctx.fs.api.token = token; ctx.fs.api.token = token;
} }
} catch (err) { } catch (err) {

70
api/app/lib/models/resource.js

@ -0,0 +1,70 @@
/* eslint-disable*/
'use strict';
module.exports = dc => {
const DataTypes = dc.ORM;
const sequelize = dc.orm;
const Resource = sequelize.define("resource", {
id: {
type: DataTypes.INTEGER,
allowNull: false,
defaultValue: null,
comment: null,
primaryKey: true,
field: "id",
autoIncrement: true
},
name: {
type: DataTypes.STRING,
allowNull: true,
defaultValue: null,
comment: "权限名称",
primaryKey: false,
field: "name",
autoIncrement: false
},
code: {
type: DataTypes.STRING,
allowNull: true,
defaultValue: null,
comment: "权限码",
primaryKey: false,
field: "code",
autoIncrement: false
},
parentId: {
type: DataTypes.INTEGER,
allowNull: true,
defaultValue: null,
comment: "父级id",
primaryKey: false,
field: "parent_id",
autoIncrement: false
},
createTime: {
type: DataTypes.DATE,
allowNull: true,
defaultValue: null,
comment: "创建时间",
primaryKey: false,
field: "create_time",
autoIncrement: false
},
order: {
type: DataTypes.INTEGER,
allowNull: true,
defaultValue: null,
comment: null,
primaryKey: false,
field: "order",
autoIncrement: false
}
}, {
tableName: "resource",
comment: "",
indexes: []
});
dc.models.Resource = Resource;
return Resource;
};

82
api/app/lib/models/role.js

@ -1,40 +1,52 @@
/* eslint-disable*/ /* eslint-disable*/
'use strict'; 'use strict';
module.exports = dc => { module.exports = dc => {
const DataTypes = dc.ORM; const DataTypes = dc.ORM;
const sequelize = dc.orm; const sequelize = dc.orm;
const Role = sequelize.define("role", { const Role = sequelize.define("role", {
id: { id: {
type: DataTypes.INTEGER, type: DataTypes.INTEGER,
allowNull: false, allowNull: false,
defaultValue: null, defaultValue: null,
comment: null, comment: null,
primaryKey: true, primaryKey: true,
field: "id", field: "id",
autoIncrement: true autoIncrement: true
}, },
name: { name: {
type: DataTypes.STRING, type: DataTypes.STRING,
allowNull: false, allowNull: true,
defaultValue: null, defaultValue: null,
comment: null, comment: "角色名称",
field: "name", primaryKey: false,
}, field: "name",
delete: { autoIncrement: false
type: DataTypes.BOOLEAN, },
allowNull: false, delete: {
defaultValue: true, type: DataTypes.BOOLEAN,
comment: null, allowNull: false,
field: "delete", defaultValue: null,
}, comment: "收否删除",
}, { primaryKey: false,
tableName: "role", field: "delete",
comment: "", autoIncrement: false
indexes: [] },
}); dataRange: {
dc.models.Role = Role; type: DataTypes.INTEGER,
allowNull: true,
return Role; defaultValue: null,
comment: "数据范围:1全公司 2本部门",
primaryKey: false,
field: "data_range",
autoIncrement: false
}
}, {
tableName: "role",
comment: "",
indexes: []
});
dc.models.Role = Role;
return Role;
}; };

43
api/app/lib/models/role_resource.js

@ -0,0 +1,43 @@
/* eslint-disable*/
'use strict';
module.exports = dc => {
const DataTypes = dc.ORM;
const sequelize = dc.orm;
const RoleResource = sequelize.define("roleResource", {
id: {
type: DataTypes.INTEGER,
allowNull: false,
defaultValue: null,
comment: null,
primaryKey: true,
field: "id",
autoIncrement: true
},
roleId: {
type: DataTypes.INTEGER,
allowNull: true,
defaultValue: null,
comment: "角色id",
primaryKey: false,
field: "role_id",
autoIncrement: false
},
resId: {
type: DataTypes.INTEGER,
allowNull: true,
defaultValue: null,
comment: "权限id",
primaryKey: false,
field: "res_id",
autoIncrement: false
}
}, {
tableName: "role_resource",
comment: "",
indexes: []
});
dc.models.RoleResource = RoleResource;
return RoleResource;
};

75
api/app/lib/models/user_role.js

@ -1,38 +1,47 @@
/* eslint-disable*/ /* eslint-disable*/
'use strict'; 'use strict';
module.exports = dc => { module.exports = dc => {
const DataTypes = dc.ORM; const DataTypes = dc.ORM;
const sequelize = dc.orm; const sequelize = dc.orm;
const UserRole = sequelize.define("user_role", { const UserRole = sequelize.define("userRole", {
id: { id: {
type: DataTypes.INTEGER, type: DataTypes.INTEGER,
allowNull: false, allowNull: false,
defaultValue: null, defaultValue: null,
comment: null, comment: null,
primaryKey: true, primaryKey: true,
field: "id", field: "id",
autoIncrement: true autoIncrement: true
}, },
roleId: { roleId: {
type: DataTypes.INTEGER, type: DataTypes.INTEGER,
allowNull: false, allowNull: true,
defaultValue: null, defaultValue: null,
comment: null, comment: "角色id",
field: "role_id", primaryKey: false,
}, field: "role_id",
userId: { autoIncrement: false
type: DataTypes.INTEGER, },
allowNull: false, userId: {
defaultValue: true, type: DataTypes.INTEGER,
comment: null, allowNull: true,
field: "user_id", defaultValue: null,
}, comment: "用户id",
}, { primaryKey: false,
tableName: "user_role", field: "user_id",
comment: "", autoIncrement: false
indexes: [] }
}); }, {
dc.models.UserRole = UserRole; tableName: "user_role",
return UserRole; comment: "",
indexes: []
});
dc.models.UserRole = UserRole;
const Role = dc.models.Role;
UserRole.belongsTo(Role, { foreignKey: 'roleId', targetKey: 'id' });
Role.hasMany(UserRole, { foreignKey: 'roleId', sourceKey: 'id' });
return UserRole;
}; };

43
api/app/lib/models/vacate_remark.js

@ -0,0 +1,43 @@
/* eslint-disable*/
'use strict';
module.exports = dc => {
const DataTypes = dc.ORM;
const sequelize = dc.orm;
const VacateRemark = sequelize.define("vacateRemark", {
id: {
type: DataTypes.INTEGER,
allowNull: false,
defaultValue: null,
comment: null,
primaryKey: true,
field: "id",
autoIncrement: true,
unique: "vacate_remark_id_uindex"
},
pepUserId: {
type: DataTypes.INTEGER,
allowNull: false,
defaultValue: null,
comment: 'pep用户id',
primaryKey: false,
field: "pep_user_id",
autoIncrement: false
},
remark: {
type: DataTypes.TEXT,
allowNull: true,
defaultValue: null,
comment: '备注',
primaryKey: false,
field: "remark",
autoIncrement: false
}
}, {
tableName: "vacate_remark",
comment: "",
indexes: []
});
dc.models.VacateRemark = VacateRemark;
return VacateRemark;
};

3
api/app/lib/routes/attendance/index.js

@ -17,4 +17,7 @@ module.exports = function (app, router, opts) {
app.fs.api.logAttr['GET/attendance/vacate/export'] = { content: '请假统计导出', visible: true }; app.fs.api.logAttr['GET/attendance/vacate/export'] = { content: '请假统计导出', visible: true };
router.get('/attendance/vacate/export', attendance.exportVacateStatistic); router.get('/attendance/vacate/export', attendance.exportVacateStatistic);
app.fs.api.logAttr['PUT/attendance/vacate/creat/remark'] = { content: '添加请假统计备注', visible: true };
router.put('/attendance/vacate/creat/remark', attendance.vacateRemark);
}; };

242
api/app/lib/schedule/attendance.js

@ -65,68 +65,121 @@ module.exports = function (app, opts) {
keys: ['leaveType'], keys: ['leaveType'],
require: true, require: true,
}, },
hrAffirmType: {
keyWord: ['核定请假类别'],
},
duration: { duration: {
keyWord: ['请假时长(小时)', '请假小时'], keyWord: ['请假时长(小时)', '请假小时'],
keys: ['leaveTime'] keys: ['leaveTime']
}, },
hrAffirmType: {
keyWord: ['核定请假类别'],
},
hrAffirmDuration: { hrAffirmDuration: {
keyWord: ['核定请假小时'], keyWord: ['核定请假小时'],
}, },
hrAffirmItem: {
array: {
hrAffirmType: { keyWord: ['核定请假类别'] },
hrAffirmDuration: { keyWord: ['核定请假小时'] }
}
},
} }
const schemaRecursionObj = ({ const schemaRecursionObj = ({ jsonSchema, keyWord, targetKeys = [], schemaPath, require, propKey, valueKey }) => {
jsonSchema, keyWord, targetKeys = [], schemaPath, require let schemaPath_ = JSON.parse(JSON.stringify(schemaPath));
}) => {
let schemaPath_ = JSON.parse(JSON.stringify(schemaPath))
if (jsonSchema.properties) { if (jsonSchema.properties) {
for (let prKey in jsonSchema.properties) { for (let prKey in jsonSchema.properties) {
if ( if (Array.isArray(keyWord)) { //分组vacateNeedData的keyWord传的是数值对象
keyWord.includes(jsonSchema.properties[prKey].title) if (keyWord.includes(jsonSchema.properties[prKey].title) || targetKeys.includes(prKey)) {
|| targetKeys.includes(prKey) if (propKey) {//分组的请假事项和请假时长放在一起,目的多条数据取值不错乱
) { const propKeyIndex = schemaPath_.findIndex(i => i.prKey == propKey);
schemaPath_.push({ if (propKeyIndex > -1) {
prKey, schemaPath_[propKeyIndex].child.push({
...jsonSchema.properties[prKey] key: valueKey,
}) prKey,
return schemaPath_ ...jsonSchema.properties[prKey]
} else if (jsonSchema.properties[prKey].properties) { })
schemaPath_.push({ }
prKey, } else {//非分组场景
...jsonSchema.properties[prKey] schemaPath_.push({
}) prKey,
schemaPath_ = schemaRecursionObj({ ...jsonSchema.properties[prKey]
jsonSchema: jsonSchema.properties[prKey], });
keyWord, }
targetKeys, return schemaPath_;
schemaPath: schemaPath_, } else if (jsonSchema.properties[prKey].properties) {
require, schemaPath_.push({
}) prKey,
if (!schemaPath_.length && require) { ...jsonSchema.properties[prKey]
console.warn('数据字段查找错误:', jsonSchema.properties); });
} schemaPath_ = schemaRecursionObj({
if (schemaPath_.length > schemaPath.length) { jsonSchema: jsonSchema.properties[prKey],
keyWord,
targetKeys,
schemaPath: schemaPath_,
require,
});
if (!schemaPath_.length && require) {
console.warn('数据字段查找错误:', jsonSchema.properties);
}
if (schemaPath_.length > schemaPath.length) {
return schemaPath_
}
} else if (jsonSchema.properties[prKey].items && jsonSchema.properties[prKey].items.properties) {
if (valueKey) {
const propKeyObj = schemaPath_.findIndex(i => i.prKey == prKey);
if (-1 == propKeyObj) {
schemaPath_.push({
prKey,
child: []
})
}
schemaPath_ = schemaRecursionObj({
jsonSchema: jsonSchema.properties[prKey].items,
keyWord,
targetKeys,
schemaPath: schemaPath_,
require,
propKey: prKey, //分组请假类型按照数组嵌套多个属性方式取值
valueKey: valueKey
})
}
if (!schemaPath_.length && require) {
console.warn('数据字段查找错误:', jsonSchema.properties);
}
return schemaPath_ return schemaPath_
} }
} else {//vacateNeedData中核定请假信息hrAffirmItem特殊处理
for (let item in keyWord) {
schemaPath_ = schemaRecursionObj({
jsonSchema: jsonSchema,
keyWord: keyWord[item].keyWord,
targetKeys,
schemaPath: schemaPath_,
require,
propKey: null,
valueKey: item //用于多条请假记录key,方便最后数组对象取值
});
}
return schemaPath_;
} }
} }
} else { } else {
return schemaPath_ return schemaPath_;
} }
} }
const dataRecursionObj = (dataObj, index, needData, lastKeyObj, nd) => { const dataRecursionObj = (dataObj, index, needData, lastKeyObj, nd) => {
const keyObj = needData[nd].schemaPath[index] const keyObj = needData[nd].schemaPath[index];
if (dataObj.hasOwnProperty(keyObj.prKey)) { if (dataObj.hasOwnProperty(keyObj.prKey)) {
if (lastKeyObj.prKey == keyObj.prKey) { if (lastKeyObj.prKey == keyObj.prKey) {
if (keyObj.child) {//schemaRecursionObj()处理时对分组用child标记,此处取值特殊处理
return dataRecursionChildObj(dataObj[keyObj.prKey], keyObj.child);
}
let gotValue = dataObj[keyObj.prKey] let gotValue = dataObj[keyObj.prKey]
if (keyObj.enum) { if (keyObj.enum) {
let vIndex = keyObj.enum.findIndex(ke => ke == gotValue) let vIndex = keyObj.enum.findIndex(ke => ke == gotValue);
gotValue = keyObj.enumNames[vIndex] gotValue = keyObj.enumNames[vIndex];
} }
return gotValue return gotValue;
} else { } else {
return dataRecursionObj( return dataRecursionObj(
dataObj[keyObj.prKey], dataObj[keyObj.prKey],
@ -139,6 +192,22 @@ module.exports = function (app, opts) {
} }
} }
const dataRecursionChildObj = (dataObj, keyObj) => {
return dataObj.map(data => {
let retObj = {};
for (let item in data) {
const keyToData = keyObj.find(k => k.prKey == item);
let value = data[item];
if (keyToData.enum) {
let vIndex = keyToData.enum.findIndex(ke => ke == value);
value = keyToData.enumNames[vIndex];
}
retObj[keyToData.key] = value;
}
return retObj;
});
}
const getData = (applyDetail, needData) => { const getData = (applyDetail, needData) => {
for (let nd in needData) { for (let nd in needData) {
if (needData[nd].noProcess) { if (needData[nd].noProcess) {
@ -148,35 +217,25 @@ module.exports = function (app, opts) {
const { jsonSchema } = JSON.parse(applyDetail.formSchema) const { jsonSchema } = JSON.parse(applyDetail.formSchema)
needData[nd].schemaPath = schemaRecursionObj({ needData[nd].schemaPath = schemaRecursionObj({
jsonSchema: jsonSchema || {}, jsonSchema: jsonSchema || {},
keyWord: needData[nd]['keyWord'], keyWord: needData[nd]['keyWord'] || needData[nd]['array'],
targetKeys: needData[nd]['keys'], targetKeys: needData[nd]['keys'],
schemaPath: [], schemaPath: [],
require: nd.require require: nd.require
}) })
if ( if (needData[nd].schemaPath && needData[nd].schemaPath.length) {
needData[nd].schemaPath const lastKeyObj = needData[nd].schemaPath[needData[nd].schemaPath.length - 1];
&& needData[nd].schemaPath.length
) {
const lastKeyObj = needData[nd]
.schemaPath[needData[nd].schemaPath.length - 1]
if (applyDetail.formData) { if (applyDetail.formData) {
const formData = JSON.parse(applyDetail.formData) const formData = JSON.parse(applyDetail.formData)
needData[nd].value = dataRecursionObj( needData[nd].value = dataRecursionObj(
formData, 0, needData, lastKeyObj, nd formData, 0, needData, lastKeyObj, nd
) )
} else { } else {
console.warn( console.warn(`表单数据缺失:[${nd}]`, applyDetail);
`表单数据缺失:[${nd}]`,
applyDetail
);
} }
} else { } else {
if (needData[nd].require) { if (needData[nd].require) {
// 记录错误 关键数据没找到 // 记录错误 关键数据没找到
console.warn( console.warn(`数据字段查找错误:${nd.needData[nd]['keyWord'].join('、')}`, jsonSchema);
`数据字段查找错误:${nd.needData[nd]['keyWord'].join('、')}`,
jsonSchema
);
} }
} }
} }
@ -218,10 +277,11 @@ module.exports = function (app, opts) {
` + ` +
` `
${existOvertimeCount || existVacateCount ? ${existOvertimeCount || existVacateCount ?
`WHERE story.create_at > '${moment().subtract(1, 'month').format('YYYY-MM-DD HH:mm:ss')}'` `WHERE story.create_at > '2023-03-16 00:00:00'`
: ''} : ''}
` `
).toPromise() ).toPromise()
// `WHERE story.create_at > '${moment().subtract(1, 'month').format('YYYY-MM-DD HH:mm:ss')}'`
let insertCount = 0, updateCount = 0, invalidCount = 0, unCompletedCount = 0, unknowCount = 0 let insertCount = 0, updateCount = 0, invalidCount = 0, unCompletedCount = 0, unknowCount = 0
for (let a of attendanceRes) { for (let a of attendanceRes) {
@ -229,29 +289,75 @@ module.exports = function (app, opts) {
if (a.processName) { if (a.processName) {
if ('COMPLETED' == a.state) { if ('COMPLETED' == a.state) {
if (a.processName.indexOf('请假') > -1) { if (a.processName.indexOf('请假') > -1) {
let needData = JSON.parse(JSON.stringify(vacateNeedData)) let needData = JSON.parse(JSON.stringify(vacateNeedData));
getData(a, needData) getData(a, needData);
const { begainTime, endTime, type, hrAffirmType, duration, hrAffirmDuration, reason } = needData
if ( const vacateDurationSecDeal = (hrAffirmDuration, duration) => {
begainTime.value && endTime.value && type.value let durationSec = 0;
&& (duration.value || hrAffirmDuration.value)
) {
let durationSec = 0
if (hrAffirmDuration.value) { if (hrAffirmDuration.value) {
durationSec = parseFloat(hrAffirmDuration.value) * 3600 durationSec = parseFloat(hrAffirmDuration.value) * 3600
} else if (duration.value) { } else if (duration.value) {
durationSec = parseFloat(duration.value) * 3600 durationSec = parseFloat(duration.value) * 3600
} }
return durationSec;
};
if ( const { begainTime, endTime, type, hrAffirmItem, duration, hrAffirmDuration, reason } = needData;
typeof durationSec != 'number'
|| isNaN(durationSec) if (begainTime.value && endTime.value && type.value && hrAffirmItem.value) {//核定请假分组,后面历史表单全部走完后,可把hrAffirmType、hrAffirmDuration取值的去掉
|| durationSec <= 0 const existRes = await models.Vacate.findOne({ where: { pepProcessStoryId: a.historyId } });
) { if (existRes) {
console.warn('请假时长计算结果错误', hrAffirmDuration, duration); updateCount++;
invalidCount++
} else { } else {
const storageD = [];
const packageDay = [];
for (let index in hrAffirmItem.value) {
const { hrAffirmDuration, hrAffirmType } = hrAffirmItem.value[index];
const durationSec = vacateDurationSecDeal({ value: hrAffirmDuration }, duration);
if (typeof durationSec != 'number' || isNaN(durationSec) || durationSec <= 0) {
console.warn('请假时长计算结果错误', hrAffirmDuration, duration);
invalidCount++;
} else {
storageD.push({
pepUserId: a.pepUserId,
pepProcessStoryId: a.historyId,
startTime: moment(begainTime.value).format('YYYY-MM-DD HH:mm:ss'),
endTime: moment(endTime.value).format('YYYY-MM-DD HH:mm:ss'),
duration: durationSec,
type: hrAffirmType,
wfProcessState: a.state,
reason: reason.value
});
packageDay.push({
index: {
day: moment(begainTime.value).format('YYYY-MM-DD'),
duration: durationSec
}
});
}
}
if (storageD.length) {
let res = await models.Vacate.bulkCreate(storageD, { returning: true });
insertCount++;
for (let item of res) {
await models.VacateDay.create({
day: moment(item.startTime).format('YYYY-MM-DD'),
duration: item.duration,
vacateId: item.id
});
}
}
}
} else if (begainTime.value && endTime.value && type.value && (duration.value || hrAffirmDuration.value)) {//历史请假表单,无核定请假分组
const durationSec = vacateDurationSecDeal(hrAffirmDuration, duration);
if (typeof durationSec != 'number' || isNaN(durationSec) || durationSec <= 0) {
console.warn('请假时长计算结果错误', hrAffirmDuration, duration);
invalidCount++;
} else {
const { hrAffirmType } = needData;
// 计算每个工作日请了多少假 // 计算每个工作日请了多少假
let begainTime_ = moment(begainTime.value) let begainTime_ = moment(begainTime.value)
let endTime_ = moment(endTime.value) let endTime_ = moment(endTime.value)

398
api/app/lib/utils/member.js

@ -4,102 +4,105 @@ const request = require('superagent');
module.exports = function (app, opts) { module.exports = function (app, opts) {
async function memberList ({ async function memberList({
keywordTarget, keyword, limit, page, state, keywordTarget, keyword, limit, page, state,
hiredateStart, hiredateEnd, marital, native, workPlace, hiredateStart, hiredateEnd, marital, native, workPlace,
orderBy, orderDirection, orderBy, orderDirection,
nowAttendanceTime, nowAttendanceTime,
overtimeDayStatisticStartDate, overtimeDayStatisticendDate, overtimeDayStatisticStartDate, overtimeDayStatisticendDate,
overtimeCountStatistic, overtimeCountStatisticStartDate, overtimeCountStatisticendDate, overtimeCountStatistic, overtimeCountStatisticStartDate, overtimeCountStatisticendDate,
vacateDayStatisticStartDate, vacateDayStatisticendDate, vacateDayStatisticStartDate, vacateDayStatisticendDate,
vacateDurationStatistic, vacateCountStatistic, vacateCountStatisticStartDate, vacateCountStatisticendDate vacateDurationStatistic, vacateCountStatistic, vacateCountStatisticStartDate, vacateCountStatisticendDate,
}) { userIds
const { judgeHoliday } = app.fs.utils }) {
const { clickHouse } = app.fs const { judgeHoliday } = app.fs.utils
const { database: pepEmis } = clickHouse.pepEmis.opts.config const { clickHouse } = app.fs
const { database: pepEmis } = clickHouse.pepEmis.opts.config
const curDay = moment().format('YYYY-MM-DD') const curDay = moment().format('YYYY-MM-DD')
const nowTime = moment() const nowTime = moment()
let whereOption = [] let whereOption = []
let whereFromSelectOption = [] let whereFromSelectOption = []
let returnEmpty = false let returnEmpty = false
if (state == 'inOffice') { if (state == 'inOffice') {
// 在岗 // 在岗
const holidayJudge = await judgeHoliday(curDay) const holidayJudge = await judgeHoliday(curDay)
if (holidayJudge) { if (holidayJudge) {
if ( if (
holidayJudge.workday holidayJudge.workday
&& nowTime.isAfter(moment(curDay + ' 08:30')) && nowTime.isAfter(moment(curDay + ' 08:30'))
&& nowTime.isBefore(moment(curDay + ' 17:30')) && nowTime.isBefore(moment(curDay + ' 17:30'))
) { ) {
// 在工作日的工作时间范围 无请假记录 // 在工作日的工作时间范围 无请假记录
whereFromSelectOption.push(`vacateStartTime = '1970-01-01 00:00:00.000000'`) whereFromSelectOption.push(`vacateStartTime = '1970-01-01 00:00:00.000000'`)
} else {
returnEmpty = true
}
} else { } else {
returnEmpty = true returnEmpty = true
} }
} else { }
returnEmpty = true if (state == 'dayoff') {
} // 放假
} const holidayJudge = await judgeHoliday(curDay)
if (state == 'dayoff') { if (holidayJudge) {
// 放假 if (
const holidayJudge = await judgeHoliday(curDay) holidayJudge.dayoff || holidayJudge.festivals
if (holidayJudge) { ) {
if ( // 在休息日范围内且无加班申请
holidayJudge.dayoff || holidayJudge.festivals whereFromSelectOption.push(`overtimeStartTime = '1970-01-01 00:00:00.000000'`)
) { } else {
// 在休息日范围内且无加班申请 returnEmpty = true
whereFromSelectOption.push(`overtimeStartTime = '1970-01-01 00:00:00.000000'`) }
} else { } else {
returnEmpty = true returnEmpty = true
} }
} else { }
returnEmpty = true if (returnEmpty) {
} return {
} count: 0,
if (returnEmpty) { rows: []
return { }
count: 0, }
rows: []
}
}
let overtimeDayStatisticWhere = [] let overtimeDayStatisticWhere = []
if (overtimeDayStatisticStartDate) { if (overtimeDayStatisticStartDate) {
overtimeDayStatisticWhere.push(`overtime_day.day >= '${moment(overtimeDayStatisticStartDate).format('YYYY-MM-DD')}'`) overtimeDayStatisticWhere.push(`overtime_day.day >= '${moment(overtimeDayStatisticStartDate).format('YYYY-MM-DD')}'`)
} }
if (overtimeDayStatisticendDate) { if (overtimeDayStatisticendDate) {
overtimeDayStatisticWhere.push(`overtime_day.day <= '${moment(overtimeDayStatisticendDate).format('YYYY-MM-DD')}'`) overtimeDayStatisticWhere.push(`overtime_day.day <= '${moment(overtimeDayStatisticendDate).format('YYYY-MM-DD')}'`)
} }
let overtimeCountStatisticWhere = [] let overtimeCountStatisticWhere = []
if (overtimeCountStatisticStartDate) { if (overtimeCountStatisticStartDate) {
overtimeCountStatisticWhere.push(`overtime.start_time >= '${moment(overtimeCountStatisticStartDate).startOf('day').format('YYYY-MM-DD HH:mm:ss')}'`) overtimeCountStatisticWhere.push(`overtime.start_time >= '${moment(overtimeCountStatisticStartDate).startOf('day').format('YYYY-MM-DD HH:mm:ss')}'`)
} }
if (overtimeCountStatisticendDate) { if (overtimeCountStatisticendDate) {
overtimeCountStatisticWhere.push(`overtime.end_time <= '${moment(overtimeCountStatisticendDate).endOf('day').format('YYYY-MM-DD HH:mm:ss')}'`) overtimeCountStatisticWhere.push(`overtime.end_time <= '${moment(overtimeCountStatisticendDate).endOf('day').format('YYYY-MM-DD HH:mm:ss')}'`)
} }
let vacateDayStatisticWhere = [] let vacateDayStatisticWhere = []
if (vacateDayStatisticStartDate) { if (vacateDayStatisticStartDate) {
vacateDayStatisticWhere.push(`vacate_day.day >= '${moment(vacateDayStatisticStartDate).format('YYYY-MM-DD')}'`) vacateDayStatisticWhere.push(`vacate_day.day >= '${moment(vacateDayStatisticStartDate).format('YYYY-MM-DD')}'`)
} }
if (vacateDayStatisticendDate) { if (vacateDayStatisticendDate) {
vacateDayStatisticWhere.push(`vacate_day.day <= '${moment(vacateDayStatisticendDate).format('YYYY-MM-DD')}'`) vacateDayStatisticWhere.push(`vacate_day.day <= '${moment(vacateDayStatisticendDate).format('YYYY-MM-DD')}'`)
} }
let vacateCountStatisticWhere = [] let vacateCountStatisticWhere = []
if (vacateCountStatisticStartDate) { if (vacateCountStatisticStartDate) {
vacateCountStatisticWhere.push(`vacate.start_time >= '${moment(vacateCountStatisticStartDate).startOf('day').format('YYYY-MM-DD HH:mm:ss')}'`) vacateCountStatisticWhere.push(`vacate.start_time >= '${moment(vacateCountStatisticStartDate).startOf('day').format('YYYY-MM-DD HH:mm:ss')}'`)
} }
if (vacateCountStatisticendDate) { if (vacateCountStatisticendDate) {
vacateCountStatisticWhere.push(`vacate.end_time <= '${moment(vacateCountStatisticendDate).endOf('day').format('YYYY-MM-DD HH:mm:ss')}'`) vacateCountStatisticWhere.push(`vacate.end_time <= '${moment(vacateCountStatisticendDate).endOf('day').format('YYYY-MM-DD HH:mm:ss')}'`)
} }
// CRAZY // CRAZY
const innerSelectQuery = ` const innerSelectQuery = `
FROM member FROM member
INNER JOIN ${pepEmis}.user AS user INNER JOIN ${pepEmis}.user AS user
ON member.pep_user_id = user.id ON member.pep_user_id = user.id
${userIds && userIds.length ? `AND user.id in (${userIds.join(',')})` : ''}
${keywordTarget == 'number' && keyword ? ` ${keywordTarget == 'number' && keyword ? `
AND user.people_code LIKE '%${keyword}%' AND user.people_code LIKE '%${keyword}%'
`: ''} `: ''}
@ -137,9 +140,9 @@ module.exports = function (app, opts) {
`: ''} `: ''}
${orderBy == 'overtimeTakeRestSum' || ${orderBy == 'overtimeTakeRestSum' ||
orderBy == 'overtimePaySum' || orderBy == 'overtimePaySum' ||
orderBy == 'overtimeSum' ? orderBy == 'overtimeSum' ?
`LEFT JOIN ( `LEFT JOIN (
SELECT SELECT
overtime.pep_user_id AS pepUserId, overtime.pep_user_id AS pepUserId,
sum(overtime_day.duration) AS duration sum(overtime_day.duration) AS duration
@ -170,8 +173,8 @@ module.exports = function (app, opts) {
`: ''} `: ''}
${vacateDurationStatistic || ${vacateDurationStatistic ||
orderBy == 'vacateSum' ? orderBy == 'vacateSum' ?
`LEFT JOIN ( `LEFT JOIN (
SELECT SELECT
vacate.pep_user_id AS pepUserId, vacate.pep_user_id AS pepUserId,
sum(vacate_day.duration) AS duration sum(vacate_day.duration) AS duration
@ -189,7 +192,7 @@ module.exports = function (app, opts) {
LEFT JOIN ( LEFT JOIN (
SELECT SELECT
vacate.pep_user_id AS pepUserId, vacate.pep_user_id AS pepUserId,
count(pep_process_story_id) AS count count(distinct pep_process_story_id) AS count
FROM vacate FROM vacate
${vacateCountStatisticWhere.length ? ` ${vacateCountStatisticWhere.length ? `
WHERE ${vacateCountStatisticWhere.join(' AND ')} WHERE ${vacateCountStatisticWhere.join(' AND ')}
@ -236,7 +239,7 @@ module.exports = function (app, opts) {
`: ''} `: ''}
` `
const userRes = await clickHouse.hr.query(` const userRes = await clickHouse.hr.query(`
SELECT SELECT
hrMember."member.pep_user_id" AS pepUserId, hrMember."member.pep_user_id" AS pepUserId,
hrMember.*, hrMember.*,
@ -253,8 +256,8 @@ module.exports = function (app, opts) {
FROM ( FROM (
SELECT SELECT
${orderBy == 'overtimeTakeRestSum' ${orderBy == 'overtimeTakeRestSum'
|| orderBy == 'overtimePaySum' || orderBy == 'overtimePaySum'
|| orderBy == 'overtimeSum' ? ` || orderBy == 'overtimeSum' ? `
overtimeDayStatistic.duration AS overtimeDayStatisticDuration, overtimeDayStatistic.duration AS overtimeDayStatisticDuration,
`: ''} `: ''}
@ -279,6 +282,26 @@ module.exports = function (app, opts) {
member.* member.*
${innerSelectQuery} ${innerSelectQuery}
ORDER BY ${orderBy == 'code' ?
'user.people_code'
: orderBy == 'hiredate'
? 'member.hiredate'
: orderBy == 'age'
? 'member.birthday'
: orderBy == 'overtimeTakeRestSum'
|| orderBy == 'overtimePaySum'
|| orderBy == 'overtimeSum' ?
'overtimeDayStatisticDuration'
: orderBy == 'overtimeCount' ?
'overtimeCount'
: orderBy == 'vacateSum' ?
'vacateDayStatisticDuration'
: orderBy == 'vacateCount' ?
'vacateCount'
: 'user.people_code'}
${orderDirection || 'ASC'}
${limit ? `LIMIT ${limit}` : ''} ${limit ? `LIMIT ${limit}` : ''}
${limit && page ? 'OFFSET ' + parseInt(limit) * parseInt(page) : ''} ${limit && page ? 'OFFSET ' + parseInt(limit) * parseInt(page) : ''}
) AS hrMember ) AS hrMember
@ -296,120 +319,103 @@ module.exports = function (app, opts) {
LEFT JOIN ${pepEmis}.department AS department LEFT JOIN ${pepEmis}.department AS department
ON department.id = department_user.department ON department.id = department_user.department
${whereOption.length ? `WHERE ${whereOption.join(' AND ')}` : ''} ${whereOption.length ? `WHERE ${whereOption.join(' AND ')}` : ''}
ORDER BY ${orderBy == 'code' ?
'user.people_code'
: orderBy == 'hiredate'
? 'hrMember."member.hiredate"'
: orderBy == 'age'
? 'hrMember."member.birthday"'
: orderBy == 'overtimeTakeRestSum'
|| orderBy == 'overtimePaySum'
|| orderBy == 'overtimeSum' ?
'hrMember.overtimeDayStatisticDuration'
: orderBy == 'overtimeCount' ?
'hrMember.overtimeCount'
: orderBy == 'vacateSum' ?
'hrMember.vacateDayStatisticDuration'
: orderBy == 'vacateCount' ?
'hrMember.vacateCount'
: 'user.people_code'}
${orderDirection || 'ASC'}
`).toPromise() `).toPromise()
const countRes = await clickHouse.hr.query(` const countRes = await clickHouse.hr.query(`
SELECT SELECT
count(member.pep_user_id) AS count count(member.pep_user_id) AS count
${innerSelectQuery} ${innerSelectQuery}
`).toPromise() `).toPromise()
return { return {
count: countRes[0].count, count: countRes[0].count,
rows: userRes rows: userRes
} }
} }
async function packageUserData (userRes, option = {}) { async function packageUserData(userRes, option = {}) {
const { judgeHoliday, } = app.fs.utils const { judgeHoliday, } = app.fs.utils
let workTime = false let workTime = false
let dayoffTime = false let dayoffTime = false
if (option.state) { if (option.state) {
const curDay = moment().format('YYYY-MM-DD') const curDay = moment().format('YYYY-MM-DD')
const nowTime = moment() const nowTime = moment()
const holidayJudge = await judgeHoliday(curDay) const holidayJudge = await judgeHoliday(curDay)
if (holidayJudge) { if (holidayJudge) {
if ( if (
holidayJudge.workday holidayJudge.workday
&& nowTime.isAfter(moment(curDay + ' 08:30')) && nowTime.isAfter(moment(curDay + ' 08:30'))
&& nowTime.isBefore(moment(curDay + ' 17:30')) && nowTime.isBefore(moment(curDay + ' 17:30'))
) { ) {
workTime = true workTime = true
} else if (holidayJudge.dayoff || holidayJudge.festivals) { } else if (holidayJudge.dayoff || holidayJudge.festivals) {
dayoffTime = true dayoffTime = true
}
} }
} }
}
let returnD = [] let returnD = []
let pepUserIds = [-1] let pepUserIds = [-1]
userRes.rows.forEach(u => { userRes.rows.forEach(u => {
let existUser = returnD.find(r => r.pepUserId == u.pepUserId) let existUser = returnD.find(r => r.pepUserId == u.pepUserId)
if (existUser) { if (existUser) {
if (u.depId && !existUser.departmrnt.some(d => d.id == u.depId)) { if (u.depId && !existUser.departmrnt.some(d => d.id == u.depId)) {
existUser.departmrnt.push({ existUser.departmrnt.push({
id: u.depId, id: u.depId,
name: u.depName name: u.depName
}) })
} }
if (u.roleId && !existUser.role.some(r => r.id == u.roleId)) { if (u.roleId && !existUser.role.some(r => r.id == u.roleId)) {
existUser.role.push({ existUser.role.push({
id: u.roleId, id: u.roleId,
name: u.roleName name: u.roleName
}) })
} }
} else { } else {
let obj = {} let obj = {}
for (let k in u) { for (let k in u) {
let nextKey = k.replace('hrMember.', '') let nextKey = k.replace('hrMember.', '')
.replace('member.', '') .replace('member.', '')
if (nextKey.includes('_')) { if (nextKey.includes('_')) {
nextKey = nextKey.toLowerCase() nextKey = nextKey.toLowerCase()
.replace( .replace(
/(_)[a-z]/g, /(_)[a-z]/g,
(L) => L.toUpperCase() (L) => L.toUpperCase()
) )
.replace(/_/g, '') .replace(/_/g, '')
} }
obj[nextKey] = u[k] == '1970-01-01 00:00:00.000000' || u[k] == '1970-01-01 08:00:00.000000' ? null : u[k] obj[nextKey] = u[k] == '1970-01-01 00:00:00.000000' || u[k] == '1970-01-01 08:00:00.000000' ? null : u[k]
}
pepUserIds.push(u.pepUserId)
// console.log("查询到的用户信息:", obj);
returnD.push({
...obj,
departmrnt: u.depId ? [{
id: u.depId,
name: u.depName
}] : [],
role: u.roleId ? [{
id: u.roleId,
name: u.roleName
}] : [],
state: option.state ?
obj['dimissionDate'] ? 'dimission' :
obj['vacateStartTime'] ? 'vacate' :
workTime ? 'inOffice' :
dayoffTime ? 'dayoff' : 'rest'
: undefined,
del: undefined,
pepuserid: undefined
})
} }
pepUserIds.push(u.pepUserId) })
console.log("查询到的用户信息:", obj); return { packageUser: returnD, pepUserIds }
returnD.push({ }
...obj,
departmrnt: u.depId ? [{
id: u.depId,
name: u.depName
}] : [],
role: u.roleId ? [{
id: u.roleId,
name: u.roleName
}] : [],
state: option.state ?
obj['dimissionDate'] ? 'dimission' :
obj['vacateStartTime'] ? 'vacate' :
workTime ? 'inOffice' :
dayoffTime ? 'dayoff' : 'rest'
: undefined,
del: undefined,
pepuserid: undefined
})
}
})
return { packageUser: returnD, pepUserIds }
}
return { return {
memberList, memberList,
packageUserData packageUserData
} }
} }

2
doc/scripts/3.11.0/add_primary_key.sql

@ -0,0 +1,2 @@
ALTER TABLE "public"."vacate_remark"
ADD PRIMARY KEY ("id");

23
doc/scripts/3.11.0/create_vacate_remark.sql

@ -0,0 +1,23 @@
-- ----------------------------
-- Table structure for vacate_remark
-- ----------------------------
-- ----------------------------
-- 创建 vacate_remark 表自增序列
-- ----------------------------
CREATE SEQUENCE "public"."vacate_remark_id_seq"
INCREMENT 1
MINVALUE 1
MAXVALUE 9223372036854775807
START 1
CACHE 1;
DROP TABLE IF EXISTS "public"."vacate_remark";
CREATE TABLE "public"."vacate_remark" (
"id" int4 NOT NULL DEFAULT nextval('vacate_remark_id_seq'::regclass),
"pep_user_id" int4 NOT NULL,
"remark" text COLLATE "pg_catalog"."default"
)
;
COMMENT ON COLUMN "public"."vacate_remark"."pep_user_id" IS 'pep用户id';
COMMENT ON COLUMN "public"."vacate_remark"."remark" IS '备注';

19
doc/scripts/tools/v3.22.1_init_HRM_resource/.vscode/launch.json

@ -0,0 +1,19 @@
{
// 使 IntelliSense
//
// 访: https://go.microsoft.com/fwlink/?linkid=830387
"version": "0.2.0",
"configurations": [
{
"type": "node",
"request": "launch",
"name": "启动程序",
"program": "${workspaceFolder}\\index.js",
"env": {
"NODE_ENV": "development",
"FS_ENV": "false",
"FS_ENV_TEST":"false"
}
}
]
}

1
doc/scripts/tools/v3.22.1_init_HRM_resource/Answer.txt

@ -0,0 +1 @@
{"人员档案管理":"PERSONNELFILEMANAGEMENT","人员档案":"PERSONNELFILES","新增档案":"ADDINGFILES","导入员工信息":"IMPORTEMPLOYEEINFORMATION","导出员工档案":"EXPORTEMPLOYEEPROFILE","编辑档案":"EDITPROFILE","删除档案":"DELETEFILES","员工信息":"EMPLOYEEINFORMATION","导出员工信息":"EXPORTEMPLOYEEINFORMATION","岗位评级":"JOBRATING","导入岗位评级":"IMPORTJOBRATINGS","部门档案管理":"DEPARTMENTARCHIVESMANAGEMENT","部门档案":"DEPARTMENTARCHIVES","假勤管理":"LEAVEMANAGEMENT","出勤统计":"ATTENDANCESTATISTICS","请假统计":"LEAVESTATISTICS","导出请假统计":"EXPORTLEAVESTATISTICS","加班统计":"OVERTIMESTATISTICS","导出加班统计":"EXPORTOVERTIMESTATISTICS","员工沟通":"EMPLOYEECOMMUNICATION","员工沟通统计":"EMPLOYEECOMMUNICATIONSTATISTICS","导出员工沟通统计":"EXPORTEMPLOYEECOMMUNICATIONSTATISTICS","招聘记录":"RECRUITMENTRECORDS","任用记录":"APPOINTMENTRECORD","销售统计":"SALESSTATISTICS","销售人员分布":"SALESPERSONNELDISTRIBUTION","新增销售人员":"ADDSALESPERSONNEL","导入销售人员分布":"IMPORTSALESPERSONNELDISTRIBUTION","培训档案":"TRAININGDOSSIER","培训资源储存库":"TRAININGRESOURCEREPOSITORY","文件夹管理":"FOLDERMANAGEMENT","上传培训资料":"UPLOADTRAININGMATERIALS","下载培训资料":"DOWNLOADTRAININGMATERIALS","删除培训资料":"DELETETRAININGMATERIALS","培训管理":"TRAININGMANAGEMENT","个人培训记录":"PERSONALTRAININGRECORDS","导入个人培训记录":"IMPORTPERSONALTRAININGRECORDS","部门培训记录":"DEPARTMENTTRAININGRECORDS","导入部门培训记录":"IMPORTDEPARTMENTTRAININGRECORDS","基本动作管理":"BASICACTIONMANAGEMENT","周报管理":"WEEKLYREPORTMANAGEMENT","导出周报":"EXPORTWEEKLYREPORT","销售日志":"SALESLOG","导出销售日志":"EXPORTSALESLOG","工程日志":"ENGINEERINGLOG","导出工程日志":"EXPORTENGINEERINGLOG","员工考核":"EMPLOYEEASSESSMENT","试用期员工考核":"EMPLOYEEASSESSMENTDURINGPROBATIONARYPERIOD","正式员工考核":"FORMALEMPLOYEEASSESSMENT","下载正式员工考核":"DOWNLOADOFFICIALEMPLOYEEASSESSMENT","中层考核":"MIDDLELEVELASSESSMENT","中层月度过程考核":"MIDDLELEVELMONTHLYPROCESSASSESSMENT","下载中层月度过程考核":"DOWNLOADMIDDLELEVELMONTHLYPROCESSASSESSMENT","中层季度考核":"MIDLEVELQUARTERLYASSESSMENT","下载中层季度考核":"DOWNLOADMIDLEVELQUARTERLYASSESSMENT","高层考核":"SENIORASSESSMENT","高层月度过程考核":"MONTHLYPROCESSASSESSMENTFORSENIORMANAGEMENT","下载高层月度过程考核":"DOWNLOADTHEMONTHLYPROCESSASSESSMENTFORSENIORMANAGEMENT","高层季度考核":"QUARTERLYASSESSMENTOFSENIORMANAGEMENT","下载高层季度考核":"DOWNLOADQUARTERLYASSESSMENTSFORSENIORMANAGEMENT","奖惩管理":"REWARDANDPUNISHMENTMANAGEMENT","奖惩信息":"REWARDANDPUNISHMENTINFORMATION"}

0
doc/scripts/tools/v3.22.1_init_HRM_resource/AnswerOld.txt

62
doc/scripts/tools/v3.22.1_init_HRM_resource/AuthCode.txt

@ -0,0 +1,62 @@
人员档案管理 : 功能 : PERSONNELFILEMANAGEMENT [1]
人员档案 : 功能 : PERSONNELFILES [1]
新增档案 : 按钮 : ADDINGFILES [1];
导入员工信息 : 按钮 : IMPORTEMPLOYEEINFORMATION [2];
导出员工档案 : 按钮 : EXPORTEMPLOYEEPROFILE [3];
编辑档案 : 按钮 : EDITPROFILE [4];
删除档案 : 按钮 : DELETEFILES [5];
员工信息 : 功能 : EMPLOYEEINFORMATION [2]
导出员工信息 : 按钮 : EXPORTEMPLOYEEINFORMATION [1];
岗位评级 : 功能 : JOBRATING [3]
导入岗位评级 : 按钮 : IMPORTJOBRATINGS [1];
部门档案管理 : 功能 : DEPARTMENTARCHIVESMANAGEMENT [2]
部门档案 : 功能 : DEPARTMENTARCHIVES [1];
假勤管理 : 功能 : LEAVEMANAGEMENT [3]
出勤统计 : 功能 : ATTENDANCESTATISTICS [1];
请假统计 : 功能 : LEAVESTATISTICS [2]
导出请假统计 : 按钮 : EXPORTLEAVESTATISTICS [1];
加班统计 : 功能 : OVERTIMESTATISTICS [3]
导出加班统计 : 按钮 : EXPORTOVERTIMESTATISTICS [1];
员工沟通 : 功能 : EMPLOYEECOMMUNICATION [4]
员工沟通统计 : 功能 : EMPLOYEECOMMUNICATIONSTATISTICS [1]
导出员工沟通统计 : 按钮 : EXPORTEMPLOYEECOMMUNICATIONSTATISTICS [1];
招聘记录 : 功能 : RECRUITMENTRECORDS [5]
任用记录 : 功能 : APPOINTMENTRECORD [1];
销售统计 : 功能 : SALESSTATISTICS [6]
销售人员分布 : 功能 : SALESPERSONNELDISTRIBUTION [1]
新增销售人员 : 按钮 : ADDSALESPERSONNEL [1];
导入销售人员分布 : 按钮 : IMPORTSALESPERSONNELDISTRIBUTION [2];
培训档案 : 功能 : TRAININGDOSSIER [7]
培训资源储存库 : 功能 : TRAININGRESOURCEREPOSITORY [1]
文件夹管理 : 按钮 : FOLDERMANAGEMENT [1];
上传培训资料 : 按钮 : UPLOADTRAININGMATERIALS [2];
下载培训资料 : 按钮 : DOWNLOADTRAININGMATERIALS [3];
删除培训资料 : 按钮 : DELETETRAININGMATERIALS [4];
培训管理 : 功能 : TRAININGMANAGEMENT [8]
个人培训记录 : 功能 : PERSONALTRAININGRECORDS [1]
导入个人培训记录 : 按钮 : IMPORTPERSONALTRAININGRECORDS [1];
部门培训记录 : 功能 : DEPARTMENTTRAININGRECORDS [2]
导入部门培训记录 : 按钮 : IMPORTDEPARTMENTTRAININGRECORDS [1];
基本动作管理 : 功能 : BASICACTIONMANAGEMENT [9]
周报管理 : 功能 : WEEKLYREPORTMANAGEMENT [1]
导出周报 : 按钮 : EXPORTWEEKLYREPORT [1];
销售日志 : 功能 : SALESLOG [2]
导出销售日志 : 按钮 : EXPORTSALESLOG [1];
工程日志 : 功能 : ENGINEERINGLOG [3]
导出工程日志 : 按钮 : EXPORTENGINEERINGLOG [1];
员工考核 : 功能 : EMPLOYEEASSESSMENT [10]
试用期员工考核 : 功能 : EMPLOYEEASSESSMENTDURINGPROBATIONARYPERIOD [1];
正式员工考核 : 功能 : FORMALEMPLOYEEASSESSMENT [2]
下载正式员工考核 : 按钮 : DOWNLOADOFFICIALEMPLOYEEASSESSMENT [1];
中层考核 : 功能 : MIDDLELEVELASSESSMENT [11]
中层月度过程考核 : 功能 : MIDDLELEVELMONTHLYPROCESSASSESSMENT [1]
下载中层月度过程考核 : 按钮 : DOWNLOADMIDDLELEVELMONTHLYPROCESSASSESSMENT [1];
中层季度考核 : 功能 : MIDLEVELQUARTERLYASSESSMENT [2]
下载中层季度考核 : 按钮 : DOWNLOADMIDLEVELQUARTERLYASSESSMENT [1];
高层考核 : 功能 : SENIORASSESSMENT [12]
高层月度过程考核 : 功能 : MONTHLYPROCESSASSESSMENTFORSENIORMANAGEMENT [1]
下载高层月度过程考核 : 按钮 : DOWNLOADTHEMONTHLYPROCESSASSESSMENTFORSENIORMANAGEMENT [1];
高层季度考核 : 功能 : QUARTERLYASSESSMENTOFSENIORMANAGEMENT [2]
下载高层季度考核 : 按钮 : DOWNLOADQUARTERLYASSESSMENTSFORSENIORMANAGEMENT [1];
奖惩管理 : 功能 : REWARDANDPUNISHMENTMANAGEMENT [13]
奖惩信息 : 功能 : REWARDANDPUNISHMENTINFORMATION [1];

12
doc/scripts/tools/v3.22.1_init_HRM_resource/Dockerfile

@ -0,0 +1,12 @@
FROM repository.anxinyun.cn/base-images/nodejs12:20.10.12.2
MAINTAINER liuxinyi "liu.xinyi@free-sun.com.cn"
COPY . /app
WORKDIR /app
RUN npm cache clean -f && npm install --production --force --registry http://10.8.30.22:7000
CMD [ "node", "index.js" ]

24
doc/scripts/tools/v3.22.1_init_HRM_resource/config.js

@ -0,0 +1,24 @@
const ENV_SY_POOL = { //商用数据库配置
user: 'FashionAdmin',
host: '10.8.40.210',
database: 'HRM',
password: '123456',
port: 5432
}
const ENV_CS_POOL = { //生产环境数据库配置
user: 'postgres',
host: '10.8.30.166',
database: 'HRM',
password: '123',
port: 5432
}
const PRO_POOL = { //研发自测用
user: 'postgres',
host: '10.8.30.166',
database: 'hr-dev',
password: '123',
port: 5432
}
module.exports = {
ENV_SY_POOL, ENV_CS_POOL, PRO_POOL
}

425
doc/scripts/tools/v3.22.1_init_HRM_resource/index.js

@ -0,0 +1,425 @@
try {
const { Pool, Client } = require('pg')
const Hex = require('crypto-js/enc-hex');
const MD5 = require('crypto-js/md5');
const request = require('superagent');
const fs = require("fs");
const args = require('args');
const moment = require('moment');
const { ENV_SY_POOL, ENV_CS_POOL, PRO_POOL } = require('./config.js')
args.option(['e', 'env'], '商用环境');
args.option(['t', 'test'], '测试环境');
args.option(['p', 'product'], '生产环境');
const flags = args.parse(process.argv);
console.log("process.env.FS_ENV:", process.env.FS_ENV);
console.log("process.env.FS_ENV_TEST:", process.env.FS_ENV_TEST);
console.log(" flags.env:", flags.env);
console.log("flags.test:", flags.test);
const FS_ENV = process.env.FS_ENV || flags.env || false;
const FS_ENV_TEST = process.env.FS_ENV_TEST || flags.test || false;
let poolSetting = PRO_POOL;
let readAnswer = true;
if (FS_ENV && 'true' == FS_ENV) {//商用
poolSetting = ENV_SY_POOL;
console.log("poolSetting:FS_ENV");
} else if (FS_ENV_TEST && 'true' == FS_ENV_TEST) {//测试
poolSetting = ENV_CS_POOL;
console.log("poolSetting:FS_ENV_TEST");
} else {//研发
poolSetting = PRO_POOL;
//对权限初始变动时,readAnswer需设为false,其他数据库执行更新时设为true
// readAnswer = false;
console.log("poolSetting:PRO_POOL");
}
const pool = new Pool(poolSetting);
const baiduInfo = {
appid: '20200917000567738',
key: 'xXm4jsuuD38JIkkhEcK6'
}
// 功能名称 alias翻译别名,show展示的名字,btn操作按钮,child子类,order排序
let resource = [
{
show: '人员档案',
alias: '人员档案管理',
order: 1,
child: [{
show: '人员档案',
child: [{
btn: '新增档案',
alias: '新增档案'
}, {
btn: '导入员工信息'
}, {
btn: '导出',
alias: '导出员工档案'
}, {
btn: '编辑档案'
}, {
btn: '删除档案'
}]
}, {
show: '员工信息',
child: [{
btn: '导出',
alias: '导出员工信息'
}]
}, {
show: '岗位评级',
child: [{
btn: '导入',
alias: '导入岗位评级'
}]
}]
}, {
show: '部门档案',
alias: '部门档案管理',
order: 2,
child: [{
show: '部门档案'
}]
}, {
show: '假勤管理',
order: 3,
child: [{
show: '出勤统计',
}, {
show: '请假统计',
child: [{
btn: '导出',
alias: '导出请假统计'
}]
}, {
show: '加班统计',
child: [{
btn: '导出',
alias: '导出加班统计'
}]
}]
}, {
show: '员工沟通',
order: 4,
child: [{
show: '员工沟通统计',
child: [{
btn: '导出',
alias: '导出员工沟通统计'
}]
}]
}, {
show: '招聘记录',
order: 5,
child: [{
show: '任用记录'
}]
}, {
show: '销售统计',
order: 6,
child: [{
show: '销售人员分布',
child: [{
btn: '新增',
alias: '新增销售人员'
}, {
btn: '导入',
alias: '导入销售人员分布'
}]
}]
}, {
show: '培训档案',
order: 7,
child: [{
show: '培训资源储存库',
child: [{
btn: '文件夹管理'
}, {
btn: '上传文件',
alias: '上传培训资料'
}, {
btn: '下载',
alias: '下载培训资料'
}, {
btn: '删除',
alias: '删除培训资料'
}]
}]
}, {
show: '培训管理',
order: 8,
child: [{
show: '个人培训记录',
child: [{
btn: '导入',
alias: '导入个人培训记录'
}]
}, {
show: '部门培训记录',
child: [{
btn: '导入',
alias: '导入部门培训记录'
}]
}]
}, {
show: '基本动作管理',
order: 9,
child: [{
show: '周报管理',
child: [{
btn: '导出',
alias: '导出周报'
}]
}, {
show: '销售日志',
child: [{
btn: '导出',
alias: '导出销售日志'
}]
}, {
show: '工程日志',
child: [{
btn: '导出',
alias: '导出工程日志'
}]
}]
}, {
show: '员工考核',
order: 10,
child: [{
show: '试用期员工考核'
}, {
show: '正式员工考核',
child: [{
btn: '下载',
alias: '下载正式员工考核'
}]
}]
}, {
show: '中层考核',
order: 11,
child: [{
show: '月度过程考核',
alias: '中层月度过程考核',
child: [{
btn: '下载',
alias: '下载中层月度过程考核'
}]
}, {
show: '季度考核',
alias: '中层季度考核',
child: [{
btn: '下载',
alias: '下载中层季度考核'
}]
}]
}, {
show: '高层考核',
order: 12,
child: [{
show: '月度过程考核',
alias: '高层月度过程考核',
child: [{
btn: '下载',
alias: '下载高层月度过程考核'
}]
}, {
show: '季度考核',
alias: '高层季度考核',
child: [{
btn: '下载',
alias: '下载高层季度考核'
}]
}]
}, {
show: '奖惩信息',
alias: '奖惩管理',
order: 13,
child: [{
show: '奖惩信息'
}]
}];
let transResult = '';
let answerObj = {};
const getAnswer = async (query) => {
let start = (new Date()).getTime();
let salt = start;
let str1 = baiduInfo.appid + query + salt + baiduInfo.key;
let sign = Hex.stringify(MD5(str1));
let answer = await request.get('http://api.fanyi.baidu.com/api/trans/vip/translate').timeout(1000 * 30).query({
q: query,
appid: baiduInfo.appid,
salt: salt,
from: 'zh',
to: 'en',
sign: sign
});
if (answer.body.error_code) {
console.warn(answer.body);
throw '百度不给力,快快debug'
}
let rslt = answer.body.trans_result;
// let upperCaseRslt = rslt[0].dst.toLowerCase().replace(/( |^)[a-z]/g, (L) => L.toUpperCase()).replace(/ /g, '');
let upperCaseRslt = rslt[0].dst.toUpperCase().replace(/ /g, '');
while (((new Date()).getTime() - start) < (1000 / 5)) {//每s只能调用10次
continue;
}
return upperCaseRslt
}
(async () => {
const client = await pool.connect()
try {
let oldAnswer = {};
let answers;
if (readAnswer) {
answers = fs.readFileSync('Answer.txt', 'utf-8')
answers = JSON.parse(answers);
fs.exists('./AnswerOld.txt', (exists) => {
if (exists) {
oldAnswer = fs.readFileSync('AnswerOld.txt', 'utf-8')
if (oldAnswer != '') {
oldAnswer = JSON.parse(oldAnswer)
} else {
oldAnswer = {}
}
} else {//文件不存在
oldAnswer = {}
}
});
} else {
oldAnswer = fs.readFileSync('Answer.txt', 'utf-8')
if ('' != oldAnswer)
oldAnswer = JSON.parse(oldAnswer)
else oldAnswer = {}
}
await client.query('BEGIN')
let answerArr = [];
const dealResource = async (resource, flag, parentId, childNum) => {
let index = 1;
for (let r of resource) {
let query = r.alias || r.show || r.btn
console.log(query);
let name = r.show || r.btn;
let answer = readAnswer ? answers[query] : await getAnswer(query)
if (answerArr.some(a => a == answer)) {
console.error(r);
console.error(`${query}译名重复了!`)
throw 'xxx'
} else {
answerArr.push(answer)
}
let res = {}
/**对比原来的code */
if (oldAnswer[query]) { //resource
res = await client.query(`SELECT * FROM resource where code = $1`, [oldAnswer[query]])
if (!res.rows.length) {
console.log(query, 'code码未找到对应值 :', oldAnswer[query])
throw `code码未找到对应值 : ${oldAnswer[query]}`;
}
if (res.rows[0].name != name || (r.order && res.rows[0].order != r.order) || res.rows[0].order != index) {
await client.query(`UPDATE resource SET name = $1 ,"order" = $2 WHERE id = $3`, [name, r.order || index, res.rows[0].id])
}
if (r.old && oldAnswer[r.old]) {//r如果原来存在
//name code互换
const { id } = res.rows[0];
res = await client.query(`SELECT * FROM resource where code = $1`, [oldAnswer[r.old]]);
await client.query(`UPDATE resource SET code = $1 , name = $2 WHERE id = $3`, [oldAnswer[r.old], r.old, id]);
if (res.rows)
await client.query(`UPDATE resource SET code = $1 , name = $2 WHERE id = $3`, [oldAnswer[query], name, res.rows[0].id])
}
if (oldAnswer[query] != answer) {//原code码和新翻译不一样
// await client.query(`UPDATE system_functions SET code = $1 WHERE code = $2`, [answer, oldAnswer[query]])
answer = oldAnswer[query]; //采用原翻译
}
if (parentId && parentId != res.rows[0].parentId) {//和库里的parent_id不一致
await client.query(`UPDATE resource SET parent_id = $1 WHERE id = $2`, [parentId, res.rows[0].id]);
}
console.log(oldAnswer[query]);
delete oldAnswer[query];
} else {
res = await client.query(`SELECT * FROM resource where code = $1`, [answer])
if (res && !res.rows.length) {
const queryText = `INSERT INTO resource(name, code, "order", create_time, parent_id)
VALUES( $1, $2, $3, $4, $5) RETURNING id`;
res = await client.query(queryText, [name, answer, r.order || index, moment(), parentId ? parentId : null]);
}
}
answerObj[query] = answer;
if (childNum) {
for (let i = 0; i < childNum; i++)
transResult += ' ';
}
transResult += `${query} : ${r.btn ? '按钮' : '功能'} : ${answer} [${r.order || index}]`
if (r.child) {
transResult += '\n';
let num = childNum || 0;
await dealResource(r.child, null, res.rows[0].id, ++num)
} else {
if (!flag)
transResult += ';\n'
}
index++;
}
}
await dealResource(resource, true, null, null)
console.log("oldAnswer", oldAnswer);
/**删除多余code */
for (let item in oldAnswer) {
if (oldAnswer[item]) {
const functionData = await client.query(`SELECT * FROM resource where code = $1`, [oldAnswer[item]]);
if (functionData.rows[0]) {
const dealChild = async (id) => {
/**判断当前的code是否是其它code的父值parent_id */
const childFunction = await client.query(`SELECT * FROM resource where parent_id = $1`, [id]);
if (childFunction.rows.length) {
const childIds = childFunction.rows.reduce((p, c) => p = p.concat(c.id), []);
for (let child of childIds) {
await dealChild(child);
await client.query(`DELETE FROM resource where id = $1`, [child]);
}
}
}
await dealChild(functionData.rows[0].id);
await client.query(`DELETE FROM resource where id = $1`, [functionData.rows[0].id]);
}
}
}
await client.query('COMMIT')
if (!readAnswer) {
const old = fs.readFileSync('Answer.txt', 'utf-8')
fs.writeFileSync('AnswerOld.txt', old, 'utf-8');
fs.writeFileSync('AuthCode.txt', transResult, 'utf-8');
fs.writeFileSync('Answer.txt', JSON.stringify(answerObj), 'utf-8');
console.log('文件生成完毕!')
}
console.log('执行完毕~')
} catch (e) {
await client.query('ROLLBACK')
console.log('执行错误~')
throw e
} finally {
client.release()
}
})().catch(e => console.error(e.stack))
} catch (error) {
console.error(error)
}

19
doc/scripts/tools/v3.22.1_init_HRM_resource/jenkinsfile

@ -0,0 +1,19 @@
pipeline {
agent {
node{
label 'jnlp-slave'
}
}
stages {
stage('Testing hrm......') {
steps {
buildName "#${BUILD_NUMBER} ~/pep/${JOB_NAME}:${IMAGE_VERSION}"
buildDescription "harbor.anxinyun.cn/pep/${JOB_NAME}:${IMAGE_VERSION}"
sh 'nerdctl build -t harbor.anxinyun.cn/pep/${JOB_NAME}:${IMAGE_VERSION} .'
sh 'nerdctl push harbor.anxinyun.cn/pep/${JOB_NAME}:${IMAGE_VERSION}'
}
}
}
}

19
doc/scripts/tools/v3.22.1_init_HRM_resource/jenkinsfile-registry

@ -0,0 +1,19 @@
pipeline {
agent {
node{
label 'jnlp-slave'
}
}
stages {
stage('Testing hrm......') {
steps {
buildName '#${BUILD_NUMBER} ~/pep/${JOB_NAME}:${IMAGE_VERSION}'
buildDescription 'registry.ngaiot.com/pep/${JOB_NAME}:${IMAGE_VERSION}'
sh 'pwd'
sh 'nerdctl build -t registry.ngaiot.com/pep/${JOB_NAME}:${IMAGE_VERSION} .'
sh 'nerdctl push registry.ngaiot.com/pep/${JOB_NAME}:${IMAGE_VERSION}'
}
}
}
}

21
doc/scripts/tools/v3.22.1_init_HRM_resource/package.json

@ -0,0 +1,21 @@
{
"name": "appkey-generator",
"version": "1.0.0",
"description": "tool",
"main": "index.js",
"scripts": {
"test": "mocha",
"start": "set NODE_ENV=development&&node index -e false -t false -p true"
},
"author": "liu",
"license": "ISC",
"dependencies": {
"args": "^3.0.7",
"crypto-js": "^3.3.0",
"pg": "^7.18.2",
"pg-hstore": "^2.3.2",
"sequelize": "^4.22.12",
"superagent": "^6.1.0",
"moment": "^2.29.4"
}
}

39
doc/scripts/tools/v3.22.1_init_HRM_resource/readme.txt

@ -0,0 +1,39 @@
//create by zhouxin on 2022.10.10
一、To 测试/研发小伙伴 (修改数据库配置、环境变量参数)
1.修改config.js数据库配置
todo-todo-todo:
1)测试:修改【ENV_SY_POOL】和【ENV_CS_POOL】定义的配置值;
2)研发:修改【PRO_POOL】定义的配置值
配置参数说明:
{
"host" : 数据库host, //默认值为 10.8.30.36"
"user" : 数据库user, //默认值为 FashionAdmin
"database" : 数据库database, // 默认值为 emis0630,
"password" : 数据库密码, //默认值为 123456",
"port" : 数据库端口号 //默认值为 5432"
}
2. 环境变量/启动参数 修改
todo-todo-todo: 根据场景设置对应环境参数的true/false, 判断顺序为:FS_ENV > FS_ENV_TEST
环境变量参数说明:
{
FS_ENV : false , //商用环境
FS_ENV_TEST : true , //测试环境
}
二、To 研发
//启动参数含义
配置参数说明:
{
e : false , //env 商用环境
t : false , //test 测试环境
p : true //product 研发环境
}
研发思路(同pm权限工具)answerOld.txt文件,用于和新更改的权限做对比
1.第一次执行时,测试需删除answeOld.text文件;研发看情况清空Answer.txt文件还是 readAnswer变量设置为true;
测试环境调用百度翻译接口有问题,更改权限码后需将answerOld.txt提交
2.开发注意:中文名称不变,两个 互换位置可以加old属性,仅限两两互换
存在bug:中文名称未更改只是换了位置的话,是不做更新的,如不换名字换位置,建议把名称调整下或者单独删除原来那条以及answeOld.text去除

24
web/client/src/components/setup.jsx

@ -5,7 +5,7 @@ import {
Checkbox, Checkbox,
} from "@douyinfe/semi-ui"; } from "@douyinfe/semi-ui";
function Setup (props) { function Setup(props) {
const { const {
close, close,
tableType, tableType,
@ -15,14 +15,13 @@ function Setup (props) {
const [check, setCheck] = useState([]); const [check, setCheck] = useState([]);
const checkboxcss = { width: "25%", height: 16, margin: "0 0 20px 0" }; const checkboxcss = { width: "25%", height: 16, margin: "0 0 20px 0" };
const checkItem = localStorage.getItem(tableType);
useEffect(() => { useEffect(() => {
// //
const checkItem = localStorage.getItem(tableType);
setCheck(checkItem ? JSON.parse(checkItem) : []) setCheck(checkItem ? JSON.parse(checkItem) : [])
ischeck(); ischeck();
}, []); }, []);
function ischeck (value) { function ischeck(value) {
if (check.length >= length) { if (check.length >= length) {
if (check.includes(value)) { if (check.includes(value)) {
return false; return false;
@ -73,7 +72,20 @@ function Setup (props) {
defaultValue={check} defaultValue={check}
aria-label="表格属性设置" aria-label="表格属性设置"
onChange={(check) => { onChange={(check) => {
setCheck(check); const flag = tableList[0].list.filter(t => t.disabled);
if (flag.length > 0) {
const some = check.filter((e) => !JSON.parse(checkItem).some((e2) => e2 === e))
let newArr = []
JSON.parse(checkItem).forEach((e, index) => {
if (index == JSON.parse(checkItem).length - 2) {
newArr = newArr.concat(some)
}
newArr.push(e)
})
setCheck(JSON.stringify(newArr) == checkItem ? check : newArr);
} else {
setCheck(check);
}
ischeck(); ischeck();
}} }}
> >
@ -105,7 +117,7 @@ function Setup (props) {
key={itm.value} key={itm.value}
value={itm.value} value={itm.value}
style={checkboxcss} style={checkboxcss}
disabled={ischeck(itm.value)} disabled={itm.disabled ? itm.disabled : ischeck(itm.value)}
> >
{itm.name} {itm.name}
</Checkbox> </Checkbox>

4
web/client/src/sections/humanAffairs/actions/index.js

@ -10,6 +10,7 @@ import * as employeeCommunication from './employeeCommunication'
import * as employeeAssessment from './employeeAssessment' import * as employeeAssessment from './employeeAssessment'
import * as role from './role' import * as role from './role'
import * as vacateRemark from './vacateRemark'
export default { export default {
...personnelFiles, ...personnelFiles,
@ -20,5 +21,6 @@ export default {
...resourceRepository, ...resourceRepository,
...employeeCommunication, ...employeeCommunication,
...employeeAssessment, ...employeeAssessment,
...role ...role,
...vacateRemark
} }

15
web/client/src/sections/humanAffairs/actions/vacateRemark.js

@ -0,0 +1,15 @@
'use strict';
import { ApiTable, basicAction } from '$utils'
export function createVacateRemark(query) {//添加请假统计备注
return (dispatch) => basicAction({
type: "put",
dispatch: dispatch,
query: query,
actionType: "PUT_VACATE_REMARK",
url: `${ApiTable.createVacateRemark}`,
msg: { option: '添加备注' },
reducer: {},
});
}

78
web/client/src/sections/humanAffairs/containers/leaveStatistics.jsx

@ -1,6 +1,7 @@
import React, { useEffect, useState, useRef, useMemo } from 'react'; import React, { useEffect, useState, useRef, useMemo } from 'react';
import { connect } from 'react-redux'; import { connect } from 'react-redux';
import { Table, Button, Pagination, Skeleton, Form, Tooltip } from '@douyinfe/semi-ui'; import { Table, Button, Pagination, Skeleton, Form, Tooltip } from '@douyinfe/semi-ui';
import VacateRemark from './vacateRemark';
import { IconSearch } from '@douyinfe/semi-icons'; import { IconSearch } from '@douyinfe/semi-icons';
import { SkeletonScreen } from "$components"; import { SkeletonScreen } from "$components";
import '../style.less' import '../style.less'
@ -26,22 +27,27 @@ const leaveStatistics = (props) => {
const [downloadUrl, setDownloadUrl] = useState('')//pdf const [downloadUrl, setDownloadUrl] = useState('')//pdf
const LEAVESTATISTICS = "leaveStatistics"; const LEAVESTATISTICS = "leaveStatistics";
const page = useRef(query.page);// const page = useRef(query.page);//
const [modalV, setModalV] = useState(false);
const [remark, setRemark] = useState(null);
const [tableList, setTableList] = useState([{ const [tableList, setTableList] = useState([{
title: '展示信息', title: '展示信息',
list: [ list: [
{ name: "姓名", value: "userName" }, { name: "姓名", value: "userName", disabled: true },
{ name: "所属部门", value: "departmrnt" }, { name: "所属部门", value: "departmrnt", disabled: true },
{ name: "职位", value: "roleName" }, { name: "职位", value: "roleName", disabled: true },
{ name: "合计请假时长", value: "vacateDayStatisticDuration" }, { name: "在职状态", value: "inStatus", disabled: true },
{ name: "合计请假次数", value: "vacateCount" }, { name: "合计请假时长", value: "vacateDayStatisticDuration", disabled: true },
{ name: "合计请假次数", value: "vacateCount", disabled: true },
{ name: "备注", value: "remark", disabled: true },
{ name: "操作", value: "operate", disabled: true },
] ]
}]);// }]);//
useEffect(() => { useEffect(() => {
localStorage.getItem(LEAVESTATISTICS) == null localStorage.getItem(LEAVESTATISTICS) == null
? localStorage.setItem( ? localStorage.setItem(
LEAVESTATISTICS, LEAVESTATISTICS,
JSON.stringify(['userName', 'departmrnt', 'roleName', 'vacateCount', 'vacateDayStatisticDuration']) JSON.stringify(['userName', 'departmrnt', 'roleName', 'inStatus', 'vacateCount', 'vacateDayStatisticDuration', 'remark', 'operate'])
) )
: ""; : "";
getAttendanceVacateTypeList() getAttendanceVacateTypeList()
@ -89,6 +95,7 @@ const leaveStatistics = (props) => {
</span> </span>
), ),
width: 200, width: 200,
fixed: true,
dataIndex: "userCode", dataIndex: "userCode",
key: "userCode", key: "userCode",
sorter: (a, b) => { }, sorter: (a, b) => { },
@ -111,6 +118,7 @@ const leaveStatistics = (props) => {
</div> </div>
), ),
width: 100, width: 100,
fixed: true,
dataIndex: "userName", dataIndex: "userName",
key: "userName", key: "userName",
render: (_, r, index) => { render: (_, r, index) => {
@ -123,6 +131,7 @@ const leaveStatistics = (props) => {
</div> </div>
), ),
width: 200, width: 200,
fixed: true,
dataIndex: "departmrnt", dataIndex: "departmrnt",
key: "departmrnt", key: "departmrnt",
render: (_, r, index) => { render: (_, r, index) => {
@ -165,6 +174,7 @@ const leaveStatistics = (props) => {
</div> </div>
), ),
width: 150, width: 150,
fixed: true,
dataIndex: "roleName", dataIndex: "roleName",
key: "roleName", key: "roleName",
render: (_, r, index) => { render: (_, r, index) => {
@ -196,6 +206,19 @@ const leaveStatistics = (props) => {
} }
</div>); </div>);
}, },
}, {
title: (
<div>
<img src="/assets/images/hrImg/V.png" alt="" style={{ width: 14, height: 14 }} /> 在职状态
</div>
),
width: 120,
fixed: true,
dataIndex: "inStatus",
key: "inStatus",
render: (_, r, index) => {
return (r.userActiveStatus == 1 ? '在职' : r.userActiveStatus == 2 ? '离职' : '特殊状态-特殊账号');
},
}, },
]; ];
for (let j = 0; j < typeList.length; j++) { for (let j = 0; j < typeList.length; j++) {
@ -234,6 +257,35 @@ const leaveStatistics = (props) => {
return (r.vacateCount ? r.vacateCount : '0') return (r.vacateCount ? r.vacateCount : '0')
}, },
}) })
column.push({
title: '备注',
width: 160,
dataIndex: "remark",
key: "remark",
render: (_, r, index) => {
return (
r.remark?.length > 22 ? <Tooltip content={r.remark}>
{r.remark.slice(0, 21) + '...'}
</Tooltip> : (r.remark ? r.remark : '无')
)
},
})
column.push({
title: '操作',
width: 160,
dataIndex: "operate",
key: "operate",
render: (_, r, index) => {
return (
<div>
<span style={{ color: '#1890FF', cursor: 'pointer' }} onClick={() => {
setModalV(true);
setRemark({ pepUserId: r.pepUserId, remark: r.remark })
}}>添加备注</span>
</div>
)
},
})
for (let i = 0; i < arr.length; i++) { for (let i = 0; i < arr.length; i++) {
let colum = column.filter((item) => { let colum = column.filter((item) => {
return item.key === arr[i]; return item.key === arr[i];
@ -255,6 +307,11 @@ const leaveStatistics = (props) => {
} }
} }
const scroll = useMemo(() => ({}), []); const scroll = useMemo(() => ({}), []);
const closeAndFetch = () => {
setModalV(false)
getAttendanceVacateList();
}
return ( return (
<> <>
<div style={{ padding: '0px 12px' }}> <div style={{ padding: '0px 12px' }}>
@ -417,12 +474,19 @@ const leaveStatistics = (props) => {
</div> </div>
</div> </div>
</div> </div>
{
modalV ? <VacateRemark
remarkData={remark}
close={() => closeAndFetch()}
onCancel={() => setModalV(false)} /> : ''
}
</div> </div>
{setup ? ( {setup ? (
<Setup <Setup
tableType={LEAVESTATISTICS} tableType={LEAVESTATISTICS}
tableList={tableList} tableList={tableList}
length={5 + mytypeList.length} length={8 + mytypeList.length}
close={() => { close={() => {
setSetup(false); setSetup(false);
attribute(mytypeList); attribute(mytypeList);

18
web/client/src/sections/humanAffairs/containers/salersDistribution/personnelDistribution.jsx

@ -12,7 +12,7 @@ const PersonnelDistribution = (props) => {
const { dispatch, actions } = props const { dispatch, actions } = props
const { humanAffairs } = actions; const { humanAffairs } = actions;
const [keywordTarget, setKeywordTarget] = useState('dep'); const [keywordTarget, setKeywordTarget] = useState('dep');
const [job, setJob] = useState('in'); const [userActiveStatus, setuserActiveStatus] = useState(1);
const [keyword, setKeyword] = useState('');// const [keyword, setKeyword] = useState('');//
const [limits, setLimits] = useState()// const [limits, setLimits] = useState()//
const [query, setQuery] = useState({ limit: 10, page: 0 }); // const [query, setQuery] = useState({ limit: 10, page: 0 }); //
@ -38,7 +38,7 @@ const PersonnelDistribution = (props) => {
let kt = keywordTarget == 'place' ? '' : keywordTarget; let kt = keywordTarget == 'place' ? '' : keywordTarget;
let k = keywordTarget == 'place' ? '' : keyword; let k = keywordTarget == 'place' ? '' : keyword;
let placeSearch = keywordTarget == 'place' ? keyword : ''; let placeSearch = keywordTarget == 'place' ? keyword : '';
dispatch(humanAffairs.getSalesList({ keywordTarget: kt, keyword: k, job, placeSearch, ...query })).then(r => { dispatch(humanAffairs.getSalesList({ keywordTarget: kt, keyword: k,userActiveStatus, placeSearch, ...query })).then(r => {
if (r.success) { if (r.success) {
setTableData(r.payload?.data?.rows); setTableData(r.payload?.data?.rows);
setLimits(r.payload?.data?.count) setLimits(r.payload?.data?.count)
@ -118,6 +118,12 @@ const PersonnelDistribution = (props) => {
let arrStr = text.map(t => t.name); let arrStr = text.map(t => t.name);
return getMultis(arrStr); return getMultis(arrStr);
} }
},{
title: '在职状态',
dataIndex: 'userActiveStatus',
key: 'userActiveStatus',
width: 200,
render: (text, r, index) => text == '1'?'在职':text == '2'?'离职':'特殊账号-特殊状态'
}, { }, {
title: '销售区域(省/直辖市)', title: '销售区域(省/直辖市)',
dataIndex: 'provinces', dataIndex: 'provinces',
@ -229,10 +235,10 @@ const PersonnelDistribution = (props) => {
</Select> </Select>
</div> </div>
<div> <div>
<Select value={job} style={{ width: 170, marginLeft: 15 }} onChange={setJob} > <Select value={userActiveStatus} style={{ width: 170, marginLeft: 15 }} onChange={setuserActiveStatus} >
<Select.Option value='in'>在职</Select.Option> <Select.Option value={1}>在职</Select.Option>
<Select.Option value='out'>离职</Select.Option> <Select.Option value={2}>离职</Select.Option>
<Select.Option value='other'>特殊账号-特殊状态</Select.Option> <Select.Option value={3}>特殊账号-特殊状态</Select.Option>
</Select> </Select>
</div> </div>
<div style={{ margin: '0px 18px' }}> <div style={{ margin: '0px 18px' }}>

48
web/client/src/sections/humanAffairs/containers/vacateRemark.js

@ -0,0 +1,48 @@
import React, { useEffect, useRef, useState } from 'react';
import { connect } from "react-redux";
import { Modal, Form } from "@douyinfe/semi-ui";
const VacateRemark = (props) => {
const { dispatch, actions, onCancel, close, remarkData } = props;
console.log(actions, 'actionsactionsactions');
const { humanAffairs } = actions;
const form = useRef();//表单
//初始化
useEffect(() => { }, []);
function handleOk() {
form.current.validate().then((values) => {
dispatch(humanAffairs.createVacateRemark({ pepUserId: remarkData.pepUserId, remark: values.remark || '' })).then((res) => {
if (res.success) {
close();
}
})
})
}
return (
<Modal title='添加备注'
visible={true}
destroyOnClose
okText='保存' width={800}
onOk={handleOk}
onCancel={onCancel}>
<Form getFormApi={(formApi) => (form.current = formApi)} labelPosition={'left'}>
<Form.TextArea
field="remark"
label='备注'
initValue={remarkData?.remark || ""}
placeholder="请输入备注"
/>
</Form>
</Modal>
)
}
function mapStateToProps(state) {
const { global } = state;
return {
actions: global.actions,
};
}
export default connect(mapStateToProps)(VacateRemark);

2
web/client/src/utils/webapi.js

@ -73,6 +73,8 @@ export const ApiTable = {
delRole:'role/del', delRole:'role/del',
getUserRoleList:'roleUser/list', getUserRoleList:'roleUser/list',
addUserRole:'roleUser/add', addUserRole:'roleUser/add',
// 请假统计添加备注
createVacateRemark: 'attendance/vacate/creat/remark'
}; };
export const RouteTable = { export const RouteTable = {

Loading…
Cancel
Save