'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, basicdata_post.name AS userPost, 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 basicdata_post ON basicdata_post.id = user.post 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, userPost: u.userPost, 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, } }) await models.SalesDistribution.update({//顺便把销售人员删了 del: true, }, { where: { pepUserId, } }) ctx.status = 204; } catch (error) { ctx.fs.logger.error(`path: ${ctx.path}, error: ${error}`); ctx.status = 400; ctx.body = { message: typeof error == 'string' ? error : undefined } } } async function 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( `start_time <= '${moment(endDate).endOf('day').format('YYYY-MM-DD HH:mm:ss')}' AND start_time >= '${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 WHERE overtime.pep_user_id = ${pepUserId} ${timeOption.length ? `AND ${timeOption.join(' AND ')}` : ''} `).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.sort((a, b) => moment(a.day) - moment(b.day)) } 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.sort((a, b) => moment(a.day) - moment(b.day)) } 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, UserAttribute } = 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', }, { title: '所属部门', key: 'departmrnt', }, { title: '职位', key: 'userJob', }, { title: '岗位', key: 'userPost', }, { title: '在职状态', key: 'userActiveStatus', }, { title: '绩点', key: 'gradePoint', }, { title: '归属机构', key: 'userOrganization', }, { title: '技术职级等级', key: 'technicalGrade', }] 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.userJob = d.userJob ? UserAttribute.jobDataSource[d.userJob - 1] : ''; d.userActiveStatus = d.userActiveStatus ? UserAttribute.activeStatusDataSource[d.userActiveStatus - 1] : ''; d.userOrganization = d.userOrganization ? UserAttribute.organizationDataSource[d.userOrganization - 1] : ''; d.idPhoto ? d.idPhoto = qiniu.domain + '/' + d.idPhoto : '' d.vitae ? d.vitae = qiniu.domain + '/' + d.vitae : '' 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; } } //岗位评级 async function getPositionRating(ctx) { try { const { models } = ctx.fs.dc; const { limit, page } = ctx.query; const findObj = { order: [["ratingTime", "desc"]] }; if (Number(limit) > 0 && Number(page) >= 0) { findObj.limit = Number(limit); findObj.offset = Number(page) * Number(limit); } const list = await models.PositionRating.findAndCountAll(findObj); if (list.rows.length) { const userIds = list.rows.map(u => u.pepUserId); /**查询user信息 */ const { clickHouse } = ctx.app.fs const { database: pepEmis } = clickHouse.pepEmis.opts.config const userRes = await clickHouse.hr.query(` SELECT user.id as userId, user.name AS userName, user.people_code AS userCode, basicDataPost.name AS userPost, department.name AS depName, department.id AS depId, user.job AS userJob, user.active_status AS userActiveStatus, user.organization AS userOrganization FROM ${pepEmis}.user AS user LEFT JOIN ${pepEmis}.basicdata_post AS basicDataPost ON ${pepEmis}.basicdata_post.id = user.post LEFT JOIN ${pepEmis}.department_user AS department_user ON department_user.user = user.id LEFT JOIN ${pepEmis}.department AS department ON department.id = department_user.department where user.id in (${userIds.join(",")} ) `).toPromise(); const rslt = []; list.rows.map(item => { const userInfo = userRes && userRes.filter(u => item.pepUserId == u.userId); item.dataValues.department = userInfo.map(u => { return { depName: u.depName, depId: u.depId } }); item.dataValues.userName = userInfo.length && userInfo[0].userName; const { theoryPassed, totalRatingPassed, ratingTime, ...rest } = item.dataValues rslt.push({ ...rest, theoryPassed: theoryPassed ? '是' : '否', totalRatingPassed: totalRatingPassed ? '是' : '否', ratingTime: moment(ratingTime).format('YYYY-MM-DD'), department: userInfo.map(u => { return { depName: u.depName, depId: u.depId } }), userName: userInfo.length && userInfo[0].userName, userPost: userInfo.length && userInfo[0].userPost }); }) ctx.body = { rows: rslt, count: list.count } } else { ctx.body = list; } ctx.status = 200; } catch (error) { ctx.fs.logger.error(`path: ${ctx.path}, error: ${error}`); ctx.status = 400; ctx.body = { message: typeof error == 'string' ? error : undefined } } } //新增岗位评级 async function postPositionRating(ctx) { let errorMsg = { message: '导入岗位绩效失败' }; const transaction = await ctx.fs.dc.orm.transaction(); try { const models = ctx.fs.dc.models; const data = ctx.request.body; //处理新增的 if (data) { const dataToUpdate = []; if (data.length) { data.map((item) => { const { pepUserId, technicalGrade } = item; dataToUpdate.push({ pepUserId, technicalGrade }); }) await models.PositionRating.bulkCreate(data, { transaction }); } else { await models.PositionRating.create(data, { transaction }); dataToUpdate.push({ pepUserId: data.pepUserId, technicalGtechnicalGrade: data.technicalGraderade }); } for (let item in dataToUpdate) { await models.Member.update({ technicalGrade: dataToUpdate[item].technicalGrade }, { where: { pepUserId: dataToUpdate[item].pepUserId }, transaction }); } } 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; } } async function delPositionRating(ctx) { const transaction = await ctx.fs.dc.orm.transaction(); try { const { models } = ctx.fs.dc; const { id } = ctx.params; const oldData = await models.PositionRating.findOne({ where: { id: id } }); if (oldData) { const positionRating = await models.PositionRating.findAll({ where: { pepUserId: oldData.pepUserId, id: { $not: oldData.id } }, attributes: ["technicalGrade"], order: [["ratingTime", "desc"]] }); //更新员工信息中的技术等级 const technicalGrade = positionRating.length ? positionRating[0].technicalGrade : undefined; await models.Member.update({ technicalGrade: technicalGrade }, { where: { pepUserId: oldData.pepUserId, del: false }, transaction }); await models.PositionRating.destroy({ where: { id: id }, transaction }); } transaction.commit(); ctx.status = 204; } catch (error) { transaction.rollback(); ctx.fs.logger.error(`path: ${ctx.path}, error: ${error}`); ctx.status = 400; ctx.body = { message: typeof error == 'string' ? error : undefined } } } module.exports = { add, edit, del, searchPepMember, list, overTimeStatistics, vacateStatistics, exportData, addMembersBulk, nativePlaceList, workPlaceList, maritalList, getPositionRating, postPositionRating, delPositionRating }