人力资源
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 

682 lines
21 KiB

'use strict';
const moment = require('moment')
const fs = require('fs');
async function add (ctx) {
try {
const { models } = ctx.fs.dc;
const {
pepUserId, idNumber, idPhoto, gender, birthday, nativePlace, marital,
politicsStatus, phoneNumber, workPlace, graduatedFrom, educationBackground, specialty, graduationDate, hiredate, turnProbationPeriod, regularDate, dimissionDate, experienceYear, occupationalHistory, vitae
} = ctx.request.body
const existMemberRes = await models.Member.findOne({
where: {
pepUserId
}
})
if (existMemberRes && !existMemberRes.del) {
throw '当前人员信息已存在'
}
let storageData = {
pepUserId, idNumber, idPhoto, gender, birthday, nativePlace, marital,
politicsStatus, phoneNumber, workPlace, graduatedFrom, educationBackground, specialty, graduationDate, hiredate, turnProbationPeriod, regularDate, dimissionDate, experienceYear, occupationalHistory, vitae,
del: false
}
if (existMemberRes && existMemberRes.del) {
await models.Member.update(storageData, {
where: {
pepUserId,
}
})
} else {
await models.Member.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, idNumber, idPhoto, gender, birthday, nativePlace, marital,
politicsStatus, phoneNumber, workPlace, graduatedFrom, educationBackground, specialty, graduationDate, hiredate, turnProbationPeriod, regularDate, dimissionDate, experienceYear, occupationalHistory, vitae
} = ctx.request.body
const existMemberRes = await models.Member.findOne({
where: {
pepUserId
}
})
if (!existMemberRes) {
throw '当前人员信息不存在'
}
let storageData = {
pepUserId, idNumber, idPhoto, gender, birthday, nativePlace, marital,
politicsStatus, phoneNumber, workPlace, graduatedFrom, educationBackground, specialty, graduationDate, hiredate, turnProbationPeriod, regularDate, dimissionDate, experienceYear, occupationalHistory, vitae,
del: false
}
await models.Member.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 searchPepMember (ctx) {
try {
const { models } = ctx.fs.dc;
const { clickHouse } = ctx.app.fs
const { keyword, code } = ctx.query
let whereOption = []
if (keyword) {
// whereOption.push(`user.id = ${keyword}`)
// whereOption.push(`user.name LIKE '${keyword}'`)
}
if (code) {
whereOption.push(`user.people_code = '${code}'`)
}
const userRes = await clickHouse.pepEmis.query(`
SELECT
user.id AS pepUserId,
user.people_code AS userCode,
user.name AS userName,
role.name AS roleName,
role.id AS roleId,
department.name AS depName,
department.id AS depId
FROM
user
LEFT JOIN user_role
ON user_role.user = user.id
LEFT JOIN role
ON role.id = user_role.role
LEFT JOIN department_user
ON department_user.user = user.id
LEFT JOIN department
ON department.id = department_user.department
WHERE
user.delete = 0
${whereOption.length ? `AND ${whereOption.join(' OR ')}` : ''}
`).toPromise()
let returnD = []
userRes.forEach(u => {
let existUser = returnD.find(r => r.pepUserId == u.pepUserId)
if (existUser) {
if (u.depId && !existUser.departmrnt.some(d => d.id == u.depId)) {
existUser.departmrnt.push({
id: u.depId,
name: u.depName
})
}
if (u.roleId && !existUser.role.some(r => r.id == u.roleId)) {
existUser.role.push({
id: u.roleId,
name: u.roleName
})
}
} else {
returnD.push({
pepUserId: u.pepUserId,
name: u.userName,
userCode: u.userCode,
departmrnt: u.depId ? [{
id: u.depId,
name: u.depName
}] : [],
role: u.roleId ? [{
id: u.roleId,
name: u.roleName
}] : [],
})
}
})
ctx.status = 200;
ctx.body = returnD
} 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.Member.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 nativePlaceList (ctx) {
// 获取已有的户籍地列表
try {
const { models } = ctx.fs.dc;
const nRes = await models.Member.findAll({
attributes: ['nativePlace'],
group: 'nativePlace',
where: {
nativePlace: { $ne: null }
}
})
ctx.status = 200;
ctx.body = nRes
} catch (error) {
ctx.fs.logger.error(`path: ${ctx.path}, error: ${error}`);
ctx.status = 400;
ctx.body = {
message: typeof error == 'string' ? error : undefined
}
}
}
async function workPlaceList (ctx) {
// 获取已有的工作地列表
try {
const { models } = ctx.fs.dc;
const wRes = await models.Member.findAll({
attributes: ['workPlace'],
group: 'workPlace',
where: {
workPlace: { $ne: null }
}
})
ctx.status = 200;
ctx.body = wRes
} catch (error) {
ctx.fs.logger.error(`path: ${ctx.path}, error: ${error}`);
ctx.status = 400;
ctx.body = {
message: typeof error == 'string' ? error : undefined
}
}
}
async function maritalList (ctx) {
// 获取已有的婚育状况列表
try {
const { models } = ctx.fs.dc;
const mRes = await models.Member.findAll({
attributes: ['marital'],
group: 'marital',
where: {
marital: { $ne: null }
}
})
ctx.status = 200;
ctx.body = mRes
} catch (error) {
ctx.fs.logger.error(`path: ${ctx.path}, error: ${error}`);
ctx.status = 400;
ctx.body = {
message: typeof error == 'string' ? error : undefined
}
}
}
async function list (ctx) {
try {
const { models } = ctx.fs.dc;
const { judgeHoliday, memberList, packageUserData } = ctx.app.fs.utils
const {
keywordTarget, keyword, limit, page, state,
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
})
let { packageUser: returnD, pepUserIds } = await packageUserData(userRes, {
state: true,
})
ctx.status = 200;
ctx.body = {
count: userRes.count,
rows: returnD
}
} catch (error) {
ctx.fs.logger.error(`path: ${ctx.path}, error: ${error}`);
ctx.status = 400;
ctx.body = {
message: typeof error == 'string' ? error : undefined
}
}
}
async function overTimeStatistics (ctx) {
try {
const { models } = ctx.fs.dc;
const { clickHouse } = ctx.app.fs
const { database: pepEmis } = clickHouse.pepEmis.opts.config
const { startDate, endDate, pepUserId } = ctx.query
const timeOption = []
if (startDate && endDate) {
timeOption.push(
`wpStory.create_at <= '${moment(endDate).endOf('day').format('YYYY-MM-DD HH:mm:ss')}'
AND wpStory.create_at >= '${moment(startDate).startOf('day').format('YYYY-MM-DD HH:mm:ss')}'`
)
}
const dataRes = await clickHouse.hr.query(`
SELECT
compensate,
create_at AS createTime,
start_time AS startTime,
end_time AS endTime,
pay_dayoff AS payDayoff,
pay_festivals AS payFestivals,
pay_workday AS payWorkday,
duration,
reason,
take_rest_dayoff AS takeRestDayoff,
take_rest_festivals AS takeRestFestivals,
take_rest_workday AS takeRestWorkday,
wpStory.id AS storyId
FROM
overtime
INNER JOIN ${pepEmis}.workflow_process_history AS wpStory
ON wpStory.id = overtime.pep_process_story_id
${timeOption.length ? `AND ${timeOption.join(' AND ')}` : ''}
WHERE overtime.pep_user_id = ${pepUserId}
`).toPromise()
const statisticRes = await clickHouse.hr.query(`
SELECT
count(overtime.id) AS overTimeCount,
sum(pay_dayoff) AS sumPayDayoff,
sum(pay_festivals) AS sumPayFestivals,
sum(pay_workday) AS sumPayWorkday,
sum(take_rest_dayoff) AS sumTakeRestDayoff,
sum(take_rest_festivals) AS sumTakeRestFestivals,
sum(take_rest_workday) AS sumTakeRestWorkday
FROM
overtime
INNER JOIN ${pepEmis}.workflow_process_history AS wpStory
ON wpStory.id = overtime.pep_process_story_id
${timeOption.length ? `AND ${timeOption.join(' AND ')}` : ''}
WHERE overtime.pep_user_id = ${pepUserId}
GROUP BY overtime.pep_user_id
`).toPromise()
const statisticDayRes = await clickHouse.hr.query(`
SELECT
overtime_day.day,
sum(overtime_day.duration) AS duration,
overtime.compensate AS compensate
FROM overtime_day
INNER JOIN overtime
ON overtime.id = overtime_day.overtime_id
AND overtime.pep_user_id = ${pepUserId}
${startDate ? `
AND overtime_day.day >= '${moment(startDate).format('YYYY-MM-DD')}'
`: ''}
${endDate ? `
AND overtime_day.day <= '${moment(endDate).format('YYYY-MM-DD')}'
` : ''}
GROUP BY overtime_day.day, overtime.compensate
`).toPromise()
let returnD = {
...(statisticRes.length ? statisticRes[0] : {}),
data: dataRes,
dayStatisticData: statisticDayRes
}
ctx.status = 200;
ctx.body = returnD
} catch (error) {
ctx.fs.logger.error(`path: ${ctx.path}, error: ${error}`);
ctx.status = 400;
ctx.body = {
message: typeof error == 'string' ? error : undefined
}
}
}
async function vacateStatistics (ctx) {
try {
const { models } = ctx.fs.dc;
const { clickHouse } = ctx.app.fs
const { database: pepEmis } = clickHouse.pepEmis.opts.config
const { startDate, endDate, pepUserId } = ctx.query
const timeOption = []
if (startDate) {
// 按发起时间
// timeOption.push(
// `wpStory.create_at >= '${moment(startDate).startOf('day').format('YYYY-MM-DD HH:mm:ss')}'`
// )
// 按请假开始时间
timeOption.push(
`start_time >= '${moment(startDate).startOf('day').format('YYYY-MM-DD HH:mm:ss')}'`
)
}
if (endDate) {
// timeOption.push(
// `wpStory.create_at <= '${moment(endDate).endOf('day').format('YYYY-MM-DD HH:mm:ss')}'`
// )
timeOption.push(
`start_time <= '${moment(endDate).endOf('day').format('YYYY-MM-DD HH:mm:ss')}'`
)
}
const dataRes = await clickHouse.hr.query(`
SELECT
type,
create_at AS createTime,
start_time AS startTime,
end_time AS endTime,
duration,
reason,
wpStory.id AS storyId
FROM
vacate
INNER JOIN ${pepEmis}.workflow_process_history AS wpStory
ON wpStory.id = vacate.pep_process_story_id
WHERE vacate.pep_user_id = ${pepUserId}
${timeOption.length ? `AND ${timeOption.join(' AND ')}` : ''}
`).toPromise()
const statisticRes = await clickHouse.hr.query(`
SELECT
type,
count(vacate.id) AS count,
sum(duration) AS sumDuration
FROM
vacate
INNER JOIN ${pepEmis}.workflow_process_history AS wpStory
ON wpStory.id = vacate.pep_process_story_id
${timeOption.length ? `AND ${timeOption.join(' AND ')}` : ''}
WHERE vacate.pep_user_id = ${pepUserId}
GROUP BY type
`).toPromise()
const statisticDayRes = await clickHouse.hr.query(`
SELECT vacate_day.day, sum(vacate_day.duration) AS duration
FROM vacate_day
INNER JOIN vacate
ON vacate.id = vacate_day.vacate_id
AND vacate.pep_user_id = ${pepUserId}
${startDate ? `
AND vacate_day.day >= '${moment(startDate).format('YYYY-MM-DD')}'
`: ''}
${endDate ? `
AND vacate_day.day <= '${moment(endDate).format('YYYY-MM-DD')}'
` : ''}
GROUP BY vacate_day.day
`).toPromise()
let returnD = {
statistic: statisticRes,
data: dataRes,
dayStatisticData: statisticDayRes
}
ctx.status = 200;
ctx.body = returnD
} 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 addMembersBulk (ctx) {
let errorMsg = { message: '导入员工信息失败' };
const transaction = await ctx.fs.dc.orm.transaction();
try {
const models = ctx.fs.dc.models;
const data = ctx.request.body;
let addMembers = [];
let editMembers = [];
let memberList = await models.Member.findAll({
attributes: ['pepUserId']
});
data.map(d => {
let exist = memberList.find(m => m.pepUserId == d.pepUserId);//项企的人员编号字段还没有
if (exist) {
editMembers.push(d);
} else {
addMembers.push(d);
}
})
//处理新增的
if (addMembers.length) {
await models.Member.bulkCreate(addMembers);
}
//处理编辑的
if (editMembers.length) {
for (let i in editMembers) {
let { pepUserId, name, idNumber, gender, birthday, nativePlace, marital,
politicsStatus, phoneNumber, workPlace, graduatedFrom, educationBackground, specialty, graduationDate,
hiredate, turnProbationPeriod, regularDate, dimissionDate, experienceYear, occupationalHistory, del } = editMembers[i];
let dataToUpdate = {
name,
idNumber,
gender,
birthday,
nativePlace,
marital,
politicsStatus,
phoneNumber,
workPlace,
graduatedFrom,
educationBackground,
specialty,
graduationDate,
hiredate,
turnProbationPeriod,
regularDate,
dimissionDate,
experienceYear,
occupationalHistory,
del
}
await models.Member.update(dataToUpdate, { where: { pepUserId: pepUserId } });
}
}
await transaction.commit();
ctx.status = 204;
} catch (error) {
await transaction.rollback();
ctx.fs.logger.error(`path: ${ctx.path}, error: ${error}`);
ctx.status = 400;
ctx.body = errorMsg;
}
}
module.exports = {
add,
edit,
del,
searchPepMember,
list,
overTimeStatistics,
vacateStatistics,
exportData,
addMembersBulk,
nativePlaceList,
workPlaceList,
maritalList,
}