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.
651 lines
20 KiB
651 lines
20 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')}'`
|
|
)
|
|
}
|
|
if (endDate) {
|
|
timeOption.push(
|
|
`wpStory.create_at <= '${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
|
|
${timeOption.length ? `AND ${timeOption.join(' AND ')}` : ''}
|
|
WHERE vacate.pep_user_id = ${pepUserId}
|
|
`).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 = '' } = ctx.query
|
|
|
|
const userRes = await memberList({ keywordTarget, keyword, limit, page, state, nowAttendanceTime: true })
|
|
|
|
const tableAttributes = models['Member'].tableAttributes
|
|
const optionKeys = keys.split(',')
|
|
|
|
let { packageUser: exportD, pepUserIds } = await packageUserData(userRes)
|
|
|
|
let preHeader = [{
|
|
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,
|
|
})
|
|
}
|
|
}
|
|
header = header.concat([{
|
|
title: '累计加班次数',
|
|
key: 'overTimeCount',
|
|
}, {
|
|
title: '累计加班总时长 / h',
|
|
key: 'overTimeDuration',
|
|
}, {
|
|
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 } = editMembers[i];
|
|
|
|
let dataToUpdate = {
|
|
name,
|
|
idNumber,
|
|
gender,
|
|
birthday,
|
|
nativePlace,
|
|
marital,
|
|
politicsStatus,
|
|
phoneNumber,
|
|
workPlace,
|
|
graduatedFrom,
|
|
educationBackground,
|
|
specialty,
|
|
graduationDate,
|
|
hiredate,
|
|
turnProbationPeriod,
|
|
regularDate,
|
|
dimissionDate,
|
|
experienceYear,
|
|
occupationalHistory
|
|
}
|
|
await models.Member.update(dataToUpdate, { where: { pepUserId: pepUserId } });
|
|
}
|
|
}
|
|
|
|
await transaction.commit();
|
|
ctx.status = 204;
|
|
} catch (error) {
|
|
await transaction.rollback();
|
|
ctx.fs.logger.error(`path: ${ctx.path}, error: ${error}`);
|
|
ctx.status = 400;
|
|
ctx.body = errorMsg;
|
|
}
|
|
}
|
|
|
|
module.exports = {
|
|
add,
|
|
edit,
|
|
del,
|
|
searchPepMember,
|
|
list,
|
|
overTimeStatistics,
|
|
vacateStatistics,
|
|
exportData,
|
|
addMembersBulk,
|
|
nativePlaceList,
|
|
workPlaceList,
|
|
maritalList,
|
|
}
|