diff --git a/api/app/lib/controllers/attendance/index.js b/api/app/lib/controllers/attendance/index.js new file mode 100644 index 0000000..c25dcca --- /dev/null +++ b/api/app/lib/controllers/attendance/index.js @@ -0,0 +1,141 @@ +'use strict'; +const moment = require('moment') + +async function overtimeStatistic (ctx) { + try { + const { models } = ctx.fs.dc; + const { clickHouse } = ctx.app.fs + const { memberList, packageUserData, overtimeStatisticListDayType } = ctx.app.fs.utils + const { + keywordTarget, keyword, limit, page, orderBy, orderDirection, + startDate, endDate, + } = ctx.query + + const userRes = await memberList({ + keywordTarget, keyword, limit, page, + orderBy, orderDirection, + overtimeDayStatisticStartDate: startDate, + overtimeDayStatisticendDate: endDate, + overtimeCountStatistic: true, + overtimeCountStatisticStartDate: startDate, + overtimeCountStatisticendDate: endDate, + }) + + let { packageUser: returnD, pepUserIds } = await packageUserData(userRes) + + const sumRes = await overtimeStatisticListDayType({ + startDate, endDate, pepUserIds + }) + + returnD.forEach(u => { + u.overtimeStatistic = sumRes.filter(s => s.pepUserId == u.pepUserId) + }) + 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 exportOvertimeStatistic (ctx) { + try { + const { models } = ctx.fs.dc; + const { clickHouse } = ctx.app.fs + const { memberList, packageUserData, overtimeStatisticListDayType } = ctx.app.fs.utils + const { + keywordTarget, keyword, limit, page, orderBy, orderDirection, + startDate, endDate, + } = ctx.query + + const userRes = await memberList({ + keywordTarget, keyword, limit, page, + orderBy, orderDirection, + overtimeDayStatisticStartDate: startDate, + overtimeDayStatisticendDate: endDate, + overtimeCountStatistic: true, + overtimeCountStatisticStartDate: startDate, + overtimeCountStatisticendDate: endDate, + }) + + let { packageUser: returnD, pepUserIds } = await packageUserData(userRes) + + const sumRes = await overtimeStatisticListDayType({ + startDate, endDate, pepUserIds + }) + + returnD.forEach(u => { + u.overtimeStatistic = sumRes.filter(s => s.pepUserId == u.pepUserId) + }) + + 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 vacateStatistic (ctx) { + try { + const { models } = ctx.fs.dc; + const { clickHouse } = ctx.app.fs + const { judgeHoliday, memberList, packageUserData, vacateStatisticListDayType } = ctx.app.fs.utils + const { + keywordTarget, keyword, limit, page, orderBy, orderDirection, + startDate, endDate, + } = ctx.query + + const userRes = await memberList({ + keywordTarget, keyword, limit, page, + orderBy, orderDirection, + vacateDayStatisticStartDate: startDate, + vacateDayStatisticendDate: endDate, + vacateDurationStatistic: true, + vacateCountStatistic: true, + vacateCountStatisticStartDate: startDate, + vacateCountStatisticendDate: endDate, + }) + + let { packageUser: returnD, pepUserIds } = await packageUserData(userRes) + + const sumRes = await vacateStatisticListDayType({ + startDate, endDate, pepUserIds + }) + + returnD.forEach(u => { + u.vacateStatistic = sumRes.filter(s => s.pepUserId == u.pepUserId) + }) + 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 + } + } +} + +module.exports = { + overtimeStatistic, + vacateStatistic, +}; \ No newline at end of file diff --git a/api/app/lib/controllers/member/index.js b/api/app/lib/controllers/member/index.js index 5235fc0..bd5af13 100644 --- a/api/app/lib/controllers/member/index.js +++ b/api/app/lib/controllers/member/index.js @@ -37,7 +37,7 @@ async function add (ctx) { ctx.status = 204; } catch (error) { - ctx.fs.logger.error(`path: ${ctx.path}, error: error`); + ctx.fs.logger.error(`path: ${ctx.path}, error: ${error}`); ctx.status = 400; ctx.body = { message: typeof error == 'string' ? error : undefined @@ -77,7 +77,7 @@ async function edit (ctx) { ctx.status = 204; } catch (error) { - ctx.fs.logger.error(`path: ${ctx.path}, error: error`); + ctx.fs.logger.error(`path: ${ctx.path}, error: ${error}`); ctx.status = 400; ctx.body = { message: typeof error == 'string' ? error : undefined @@ -161,7 +161,7 @@ async function searchPepMember (ctx) { ctx.status = 200; ctx.body = returnD } catch (error) { - ctx.fs.logger.error(`path: ${ctx.path}, error: error`); + ctx.fs.logger.error(`path: ${ctx.path}, error: ${error}`); ctx.status = 400; ctx.body = { message: typeof error == 'string' ? error : undefined @@ -184,7 +184,7 @@ async function del (ctx) { ctx.status = 204; } catch (error) { - ctx.fs.logger.error(`path: ${ctx.path}, error: error`); + ctx.fs.logger.error(`path: ${ctx.path}, error: ${error}`); ctx.status = 400; ctx.body = { message: typeof error == 'string' ? error : undefined @@ -208,7 +208,7 @@ async function nativePlaceList (ctx) { ctx.status = 200; ctx.body = nRes } catch (error) { - ctx.fs.logger.error(`path: ${ctx.path}, error: error`); + ctx.fs.logger.error(`path: ${ctx.path}, error: ${error}`); ctx.status = 400; ctx.body = { message: typeof error == 'string' ? error : undefined @@ -232,7 +232,7 @@ async function workPlaceList (ctx) { ctx.status = 200; ctx.body = wRes } catch (error) { - ctx.fs.logger.error(`path: ${ctx.path}, error: error`); + ctx.fs.logger.error(`path: ${ctx.path}, error: ${error}`); ctx.status = 400; ctx.body = { message: typeof error == 'string' ? error : undefined @@ -256,7 +256,7 @@ async function maritalList (ctx) { ctx.status = 200; ctx.body = mRes } catch (error) { - ctx.fs.logger.error(`path: ${ctx.path}, error: error`); + ctx.fs.logger.error(`path: ${ctx.path}, error: ${error}`); ctx.status = 400; ctx.body = { message: typeof error == 'string' ? error : undefined @@ -267,82 +267,21 @@ async function maritalList (ctx) { async function list (ctx) { try { const { models } = ctx.fs.dc; - const { judgeHoliday, memberList } = ctx.app.fs.utils + const { judgeHoliday, memberList, packageUserData } = ctx.app.fs.utils const { keywordTarget, keyword, limit, page, state, - hiredateStart, hiredateEnd, marital, native, workPlace + hiredateStart, hiredateEnd, marital, native, workPlace, + orderBy, orderDirection, } = ctx.query - const curDay = moment().format('YYYY-MM-DD') - const nowTime = moment() - const holidayJudge = await judgeHoliday(curDay) - let workTime = false - let dayoffTime = false - if (holidayJudge) { - if ( - holidayJudge.workday - && nowTime.isAfter(moment(curDay + ' 08:30')) - && nowTime.isBefore(moment(curDay + ' 17:30')) - ) { - workTime = true - } else if (holidayJudge.dayoff || holidayJudge.festivals) { - dayoffTime = true - } - } const userRes = await memberList({ keywordTarget, keyword, limit, page, state, - hiredateStart, hiredateEnd, marital, native, workPlace + hiredateStart, hiredateEnd, marital, native, workPlace, + orderBy, orderDirection, nowAttendanceTime: true }) - let returnD = [] - userRes.rows.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 { - let obj = {} - for (let k in u) { - let nextKey = k.replace('hrMember.', '') - .replace('member.', '') - if (nextKey.includes('_')) { - nextKey = nextKey.toLowerCase() - .replace( - /(_)[a-z]/g, - (L) => L.toUpperCase() - ) - .replace(/_/g, '') - } - obj[nextKey] = u[k] == '1970-01-01 00:00:00.000000' ? null : u[k] - } - returnD.push({ - ...obj, - departmrnt: u.depId ? [{ - id: u.depId, - name: u.depName - }] : [], - role: u.roleId ? [{ - id: u.roleId, - name: u.roleName - }] : [], - state: obj['dimissionDate'] ? 'dimission' : - obj['vacateStartTime'] ? 'vacate' : - workTime ? 'inOffice' : - dayoffTime ? 'dayoff' : 'rest', - del: undefined, - pepuserid: undefined, - }) - } + let { packageUser: returnD, pepUserIds } = await packageUserData(userRes, { + state: true, }) ctx.status = 200; @@ -351,7 +290,7 @@ async function list (ctx) { rows: returnD } } catch (error) { - ctx.fs.logger.error(`path: ${ctx.path}, error: error`); + ctx.fs.logger.error(`path: ${ctx.path}, error: ${error}`); ctx.status = 400; ctx.body = { message: typeof error == 'string' ? error : undefined @@ -369,8 +308,8 @@ async function overTimeStatistics (ctx) { const timeOption = [] if (startDate && endDate) { timeOption.push( - `wpStory.create_at <= '${moment(endDate).format('YYYY-MM-DD HH:mm:ss')}' - AND wpStory.create_at > '${moment(startDate).format('YYYY-MM-DD HH:mm:ss')}'` + `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(` @@ -440,7 +379,7 @@ async function overTimeStatistics (ctx) { ctx.status = 200; ctx.body = returnD } catch (error) { - ctx.fs.logger.error(`path: ${ctx.path}, error: error`); + ctx.fs.logger.error(`path: ${ctx.path}, error: ${error}`); ctx.status = 400; ctx.body = { message: typeof error == 'string' ? error : undefined @@ -458,12 +397,12 @@ async function vacateStatistics (ctx) { const timeOption = [] if (startDate) { timeOption.push( - `wpStory.create_at > '${moment(startDate).format('YYYY-MM-DD HH:mm:ss')}'` + `wpStory.create_at > '${moment(startDate).startOf('day').format('YYYY-MM-DD HH:mm:ss')}'` ) } if (endDate) { timeOption.push( - `wpStory.create_at <= '${moment(endDate).format('YYYY-MM-DD HH:mm:ss')}'` + `wpStory.create_at <= '${moment(endDate).endOf('day').format('YYYY-MM-DD HH:mm:ss')}'` ) } const dataRes = await clickHouse.hr.query(` @@ -520,7 +459,7 @@ async function vacateStatistics (ctx) { ctx.status = 200; ctx.body = returnD } catch (error) { - ctx.fs.logger.error(`path: ${ctx.path}, error: error`); + ctx.fs.logger.error(`path: ${ctx.path}, error: ${error}`); ctx.status = 400; ctx.body = { message: typeof error == 'string' ? error : undefined @@ -532,62 +471,15 @@ async function exportData (ctx) { try { const { models } = ctx.fs.dc; const { clickHouse, opts: { qiniu } } = ctx.app.fs - const { simpleExcelDown, memberList } = ctx.app.fs.utils + 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 }) + const userRes = await memberList({ keywordTarget, keyword, limit, page, state, nowAttendanceTime: true }) const tableAttributes = models['Member'].tableAttributes const optionKeys = keys.split(',') - let exportD = [] - let pepUserIds = [] - userRes.rows.forEach(u => { - let existUser = exportD.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 { - let obj = {} - for (let k in u) { - let nextKey = k.replace('hrMember.', '') - .replace('member.', '') - if (nextKey.includes('_')) { - nextKey = nextKey.toLowerCase() - .replace( - /(_)[a-z]/g, - (L) => L.toUpperCase() - ) - .replace(/_/g, '') - } - obj[nextKey] = u[k] == '1970-01-01 00:00:00.000000' ? null : u[k] - } - pepUserIds.push(u.pepUserId) - exportD.push({ - ...obj, - departmrnt: u.depId ? [{ - id: u.depId, - name: u.depName - }] : [], - role: u.roleId ? [{ - id: u.roleId, - name: u.roleName - }] : [], - del: undefined, - pepuserid: undefined - }) - } - }) + let { packageUser: exportD, pepUserIds } = await packageUserData(userRes) let preHeader = [{ title: '姓名', @@ -668,7 +560,7 @@ async function exportData (ctx) { ctx.set('Content-disposition', 'attachment; filename=' + encodeURI(fileName)); ctx.body = fileData; } catch (error) { - ctx.fs.logger.error(`path: ${ctx.path}, error: error`); + ctx.fs.logger.error(`path: ${ctx.path}, error: ${error}`); ctx.status = 400; ctx.body = { message: typeof error == 'string' ? error : undefined diff --git a/api/app/lib/models/member.js b/api/app/lib/models/member.js index 3171167..910bad5 100644 --- a/api/app/lib/models/member.js +++ b/api/app/lib/models/member.js @@ -2,213 +2,213 @@ 'use strict'; module.exports = dc => { - const DataTypes = dc.ORM; - const sequelize = dc.orm; - const Member = sequelize.define("member", { - pepUserId: { - type: DataTypes.INTEGER, - allowNull: false, - defaultValue: null, - comment: "项企用户id", - primaryKey: true, - field: "pep_user_id", - autoIncrement: false, - unique: "member_pep_user_id_uindex" - }, - idNumber: { - type: DataTypes.STRING, - allowNull: true, - defaultValue: null, - comment: "证件号", - primaryKey: false, - field: "id_number", - autoIncrement: false - }, - idPhoto: { - type: DataTypes.STRING, - allowNull: true, - defaultValue: null, - comment: "证件照", - primaryKey: false, - field: "id_photo", - autoIncrement: false - }, - gender: { - type: DataTypes.STRING, - allowNull: true, - defaultValue: null, - comment: "性别", - primaryKey: false, - field: "gender", - autoIncrement: false - }, - birthday: { - type: DataTypes.DATEONLY, - allowNull: true, - defaultValue: null, - comment: "出生年月", - primaryKey: false, - field: "birthday", - autoIncrement: false - }, - nativePlace: { - type: DataTypes.STRING, - allowNull: true, - defaultValue: null, - comment: "籍贯", - primaryKey: false, - field: "native_place", - autoIncrement: false - }, - marital: { - type: DataTypes.STRING, - allowNull: true, - defaultValue: null, - comment: "婚育状态", - primaryKey: false, - field: "marital", - autoIncrement: false - }, - politicsStatus: { - type: DataTypes.STRING, - allowNull: true, - defaultValue: null, - comment: "整治面貌", - primaryKey: false, - field: "politics_status", - autoIncrement: false - }, - phoneNumber: { - type: DataTypes.STRING, - allowNull: true, - defaultValue: null, - comment: null, - primaryKey: false, - field: "phone_number", - autoIncrement: false - }, - workPlace: { - type: DataTypes.STRING, - allowNull: true, - defaultValue: null, - comment: "工作地点", - primaryKey: false, - field: "work_place", - autoIncrement: false - }, - graduatedFrom: { - type: DataTypes.STRING, - allowNull: true, - defaultValue: null, - comment: "毕业院校", - primaryKey: false, - field: "graduated_from", - autoIncrement: false - }, - educationBackground: { - type: DataTypes.STRING, - allowNull: true, - defaultValue: null, - comment: "学历", - primaryKey: false, - field: "education_background", - autoIncrement: false - }, - specialty: { - type: DataTypes.STRING, - allowNull: true, - defaultValue: null, - comment: "专业", - primaryKey: false, - field: "specialty", - autoIncrement: false - }, - graduationDate: { - type: DataTypes.DATEONLY, - allowNull: true, - defaultValue: null, - comment: "毕业时间", - primaryKey: false, - field: "graduation_date", - autoIncrement: false - }, - hiredate: { - type: DataTypes.DATEONLY, - allowNull: true, - defaultValue: null, - comment: "入职时间", - primaryKey: false, - field: "hiredate", - autoIncrement: false - }, - turnProbationPeriod: { - type: DataTypes.DATEONLY, - allowNull: true, - defaultValue: null, - comment: "转试用期时间", - primaryKey: false, - field: "turn_probation_period", - autoIncrement: false - }, - regularDate: { - type: DataTypes.DATEONLY, - allowNull: true, - defaultValue: null, - comment: "转正时间", - primaryKey: false, - field: "regular_date", - autoIncrement: false - }, - dimissionDate: { - type: DataTypes.DATEONLY, - allowNull: true, - defaultValue: null, - comment: "离职时间", - primaryKey: false, - field: "dimission_date", - autoIncrement: false - }, - experienceYear: { - type: DataTypes.DOUBLE, - allowNull: true, - defaultValue: null, - comment: "工作经验/年", - primaryKey: false, - field: "experience_year", - autoIncrement: false - }, - occupationalHistory: { - type: DataTypes.TEXT, - allowNull: true, - defaultValue: null, - comment: "工作经历", - primaryKey: false, - field: "occupational_history", - autoIncrement: false - }, - vitae: { - type: DataTypes.STRING, - allowNull: true, - defaultValue: null, - comment: "简历", - primaryKey: false, - field: "vitae", - autoIncrement: false - }, - del: { - type: DataTypes.BOOLEAN, - allowNull: true, - defaultValue: null, - comment: null, - primaryKey: false, - field: "del", - autoIncrement: false - } - }, { - tableName: "member", - comment: "", - indexes: [] - }); - dc.models.Member = Member; - return Member; + const DataTypes = dc.ORM; + const sequelize = dc.orm; + const Member = sequelize.define("member", { + pepUserId: { + type: DataTypes.INTEGER, + allowNull: false, + defaultValue: null, + comment: "项企用户id", + primaryKey: true, + field: "pep_user_id", + autoIncrement: false, + unique: "member_pep_user_id_uindex" + }, + idNumber: { + type: DataTypes.STRING, + allowNull: true, + defaultValue: null, + comment: "证件号", + primaryKey: false, + field: "id_number", + autoIncrement: false + }, + idPhoto: { + type: DataTypes.STRING, + allowNull: true, + defaultValue: null, + comment: "证件照", + primaryKey: false, + field: "id_photo", + autoIncrement: false + }, + gender: { + type: DataTypes.STRING, + allowNull: true, + defaultValue: null, + comment: "性别", + primaryKey: false, + field: "gender", + autoIncrement: false + }, + birthday: { + type: DataTypes.DATEONLY, + allowNull: true, + defaultValue: null, + comment: "出生年月", + primaryKey: false, + field: "birthday", + autoIncrement: false + }, + nativePlace: { + type: DataTypes.STRING, + allowNull: true, + defaultValue: null, + comment: "籍贯", + primaryKey: false, + field: "native_place", + autoIncrement: false + }, + marital: { + type: DataTypes.STRING, + allowNull: true, + defaultValue: null, + comment: "婚育状态", + primaryKey: false, + field: "marital", + autoIncrement: false + }, + politicsStatus: { + type: DataTypes.STRING, + allowNull: true, + defaultValue: null, + comment: "整治面貌", + primaryKey: false, + field: "politics_status", + autoIncrement: false + }, + phoneNumber: { + type: DataTypes.STRING, + allowNull: true, + defaultValue: null, + comment: '联系方式', + primaryKey: false, + field: "phone_number", + autoIncrement: false + }, + workPlace: { + type: DataTypes.STRING, + allowNull: true, + defaultValue: null, + comment: "工作地点", + primaryKey: false, + field: "work_place", + autoIncrement: false + }, + graduatedFrom: { + type: DataTypes.STRING, + allowNull: true, + defaultValue: null, + comment: "毕业院校", + primaryKey: false, + field: "graduated_from", + autoIncrement: false + }, + educationBackground: { + type: DataTypes.STRING, + allowNull: true, + defaultValue: null, + comment: "学历", + primaryKey: false, + field: "education_background", + autoIncrement: false + }, + specialty: { + type: DataTypes.STRING, + allowNull: true, + defaultValue: null, + comment: "专业", + primaryKey: false, + field: "specialty", + autoIncrement: false + }, + graduationDate: { + type: DataTypes.DATEONLY, + allowNull: true, + defaultValue: null, + comment: "毕业时间", + primaryKey: false, + field: "graduation_date", + autoIncrement: false + }, + hiredate: { + type: DataTypes.DATEONLY, + allowNull: true, + defaultValue: null, + comment: "入职时间", + primaryKey: false, + field: "hiredate", + autoIncrement: false + }, + turnProbationPeriod: { + type: DataTypes.DATEONLY, + allowNull: true, + defaultValue: null, + comment: "转试用期时间", + primaryKey: false, + field: "turn_probation_period", + autoIncrement: false + }, + regularDate: { + type: DataTypes.DATEONLY, + allowNull: true, + defaultValue: null, + comment: "转正时间", + primaryKey: false, + field: "regular_date", + autoIncrement: false + }, + dimissionDate: { + type: DataTypes.DATEONLY, + allowNull: true, + defaultValue: null, + comment: "离职时间", + primaryKey: false, + field: "dimission_date", + autoIncrement: false + }, + experienceYear: { + type: DataTypes.DOUBLE, + allowNull: true, + defaultValue: null, + comment: "工作经验/年", + primaryKey: false, + field: "experience_year", + autoIncrement: false + }, + occupationalHistory: { + type: DataTypes.TEXT, + allowNull: true, + defaultValue: null, + comment: "工作经历", + primaryKey: false, + field: "occupational_history", + autoIncrement: false + }, + vitae: { + type: DataTypes.STRING, + allowNull: true, + defaultValue: null, + comment: "简历", + primaryKey: false, + field: "vitae", + autoIncrement: false + }, + del: { + type: DataTypes.BOOLEAN, + allowNull: true, + defaultValue: null, + comment: null, + primaryKey: false, + field: "del", + autoIncrement: false + } + }, { + tableName: "member", + comment: "", + indexes: [] + }); + dc.models.Member = Member; + return Member; }; \ No newline at end of file diff --git a/api/app/lib/routes/attendance/index.js b/api/app/lib/routes/attendance/index.js new file mode 100644 index 0000000..9448750 --- /dev/null +++ b/api/app/lib/routes/attendance/index.js @@ -0,0 +1,11 @@ +'use strict'; + +const attendance = require('../../controllers/attendance'); + +module.exports = function (app, router, opts) { + app.fs.api.logAttr['GET/attendance/overtime'] = { content: '加班统计', visible: true }; + router.get('/attendance/overtime', attendance.overtimeStatistic); + + app.fs.api.logAttr['GET/attendance/vacate'] = { content: '请假统计', visible: true }; + router.get('/attendance/vacate', attendance.vacateStatistic); +}; \ No newline at end of file diff --git a/api/app/lib/utils/attendance.js b/api/app/lib/utils/attendance.js new file mode 100644 index 0000000..03aac20 --- /dev/null +++ b/api/app/lib/utils/attendance.js @@ -0,0 +1,68 @@ +'use strict'; +const moment = require('moment') +const request = require('superagent'); + +module.exports = function (app, opts) { + + async function overtimeStatisticListDayType ({ + startDate, endDate, pepUserIds = [] + }) { + const { clickHouse } = app.fs + + const sumRes = await clickHouse.hr.query(` + SELECT + overtime.pep_user_id AS pepUserId, + holiday.type AS dayType, + overtime.compensate AS compensate, + sum(overtime_day.duration) AS duration + FROM overtime_day + INNER JOIN overtime + ON overtime.id = overtime_day.overtime_id + AND overtime.pep_user_id IN (${pepUserIds.join(',')}) + LEFT JOIN holiday + ON holiday.day = overtime_day.day + WHERE overtime.pep_user_id IN (${pepUserIds.join(',')}) + ${startDate ? ` + AND overtime_day.day >= '${moment(startDate).format('YYYY-MM-DD')}' + `: ''} + ${endDate ? ` + AND overtime_day.day <= '${moment(endDate).format('YYYY-MM-DD')}' + ` : ''} + GROUP BY holiday.type, overtime.compensate, overtime.pep_user_id + `).toPromise() + + return sumRes + } + + async function vacateStatisticListDayType ({ + startDate, endDate, pepUserIds = [] + }) { + const { clickHouse } = app.fs + + const sumRes = await clickHouse.hr.query(` + SELECT + vacate.pep_user_id AS pepUserId, + vacate.type AS type, + sum(vacate_day.duration) AS duration + FROM vacate_day + INNER JOIN vacate + ON vacate.id = vacate_day.vacate_id + AND vacate.pep_user_id IN (${pepUserIds.join(',')}) + WHERE vacate.pep_user_id IN (${pepUserIds.join(',')}) + ${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.type, vacate.pep_user_id + `).toPromise() + + return sumRes + } + + return { + overtimeStatisticListDayType, + vacateStatisticListDayType, + } +} \ No newline at end of file diff --git a/api/app/lib/utils/member.js b/api/app/lib/utils/member.js index 85d353d..d67295d 100644 --- a/api/app/lib/utils/member.js +++ b/api/app/lib/utils/member.js @@ -6,7 +6,13 @@ module.exports = function (app, opts) { async function memberList ({ keywordTarget, keyword, limit, page, state, - hiredateStart, hiredateEnd, marital, native, workPlace + hiredateStart, hiredateEnd, marital, native, workPlace, + orderBy, orderDirection, + nowAttendanceTime, + overtimeDayStatisticStartDate, overtimeDayStatisticendDate, + overtimeCountStatistic, overtimeCountStatisticStartDate, overtimeCountStatisticendDate, + vacateDayStatisticStartDate, vacateDayStatisticendDate, + vacateDurationStatistic, vacateCountStatistic, vacateCountStatisticStartDate, vacateCountStatisticendDate }) { const { judgeHoliday } = app.fs.utils const { clickHouse } = app.fs @@ -58,43 +64,141 @@ module.exports = function (app, opts) { } } + let overtimeDayStatisticWhere = [] + if (overtimeDayStatisticStartDate) { + overtimeDayStatisticWhere.push(`overtime_day.day >= '${moment(overtimeDayStatisticStartDate).format('YYYY-MM-DD')}'`) + } + if (overtimeDayStatisticendDate) { + overtimeDayStatisticWhere.push(`overtime_day.day <= '${moment(overtimeDayStatisticendDate).format('YYYY-MM-DD')}'`) + } + + let overtimeCountStatisticWhere = [] + if (overtimeCountStatisticStartDate) { + overtimeCountStatisticWhere.push(`overtime.start_time >= '${moment(overtimeCountStatisticStartDate).format('YYYY-MM-DD')}'`) + } + if (overtimeCountStatisticendDate) { + overtimeCountStatisticWhere.push(`overtime.end_time <= '${moment(overtimeCountStatisticendDate).format('YYYY-MM-DD')}'`) + } + + let vacateDayStatisticWhere = [] + if (vacateDayStatisticStartDate) { + vacateDayStatisticWhere.push(`vacate_day.day >= '${moment(vacateDayStatisticStartDate).format('YYYY-MM-DD')}'`) + } + if (vacateDayStatisticendDate) { + vacateDayStatisticWhere.push(`vacate_day.day <= '${moment(vacateDayStatisticendDate).format('YYYY-MM-DD')}'`) + } + + let vacateCountStatisticWhere = [] + if (vacateCountStatisticStartDate) { + vacateCountStatisticWhere.push(`vacate.start_time >= '${moment(vacateCountStatisticStartDate).format('YYYY-MM-DD')}'`) + } + if (vacateCountStatisticendDate) { + vacateCountStatisticWhere.push(`vacate.end_time <= '${moment(vacateCountStatisticendDate).format('YYYY-MM-DD')}'`) + } + // CRAZY const innerSelectQuery = ` FROM member INNER JOIN ${pepEmis}.user AS user ON member.pep_user_id = user.id ${keywordTarget == 'number' && keyword ? ` - AND toString(user.id) LIKE '%${keyword}%' + AND user.people_code LIKE '%${keyword}%' `: ''} ${keywordTarget == 'name' && keyword ? ` AND user.name LIKE '%${keyword}%' `: ''} - ${state == 'vacate' ? 'INNER' : 'LEFT'} JOIN ( - SELECT - pep_user_id, - any(start_time) AS vacateStartTime, - any(end_time) AS vacateEndTime - FROM vacate - WHERE - start_time <= '${nowTime.format('YYYY-MM-DD HH:mm:ss')}' - AND end_time > '${nowTime.format('YYYY-MM-DD HH:mm:ss')}' - GROUP BY pep_user_id - ) AS hrVacate - ON hrVacate.pep_user_id = member.pep_user_id - - LEFT JOIN ( - SELECT - pep_user_id, - any(start_time) AS overtimeStartTime, - any(end_time) AS overtimeEndTime - FROM overtime - WHERE - start_time <= '${nowTime.format('YYYY-MM-DD HH:mm:ss')}' - AND end_time > '${nowTime.format('YYYY-MM-DD HH:mm:ss')}' - GROUP BY pep_user_id - ) AS hrOvertime - ON hrOvertime.pep_user_id = member.pep_user_id - - WHERE + + ${nowAttendanceTime ? ` + ${state == 'vacate' ? 'INNER' : 'LEFT'} JOIN ( + SELECT + pep_user_id, + any(start_time) AS vacateStartTime, + any(end_time) AS vacateEndTime + FROM vacate + WHERE + start_time <= '${nowTime.format('YYYY-MM-DD HH:mm:ss')}' + AND end_time > '${nowTime.format('YYYY-MM-DD HH:mm:ss')}' + GROUP BY pep_user_id + ) AS hrVacate + ON hrVacate.pep_user_id = member.pep_user_id + `: ''} + ${nowAttendanceTime ? ` + LEFT JOIN ( + SELECT + pep_user_id, + any(start_time) AS overtimeStartTime, + any(end_time) AS overtimeEndTime + FROM overtime + WHERE + start_time <= '${nowTime.format('YYYY-MM-DD HH:mm:ss')}' + AND end_time > '${nowTime.format('YYYY-MM-DD HH:mm:ss')}' + GROUP BY pep_user_id + ) AS hrOvertime + ON hrOvertime.pep_user_id = member.pep_user_id + `: ''} + + ${orderBy == 'overtimeTakeRestSum' || + orderBy == 'overtimePaySum' || + orderBy == 'overtimeSum' ? + `LEFT JOIN ( + SELECT + overtime.pep_user_id AS pepUserId, + sum(overtime_day.duration) AS duration + FROM overtime_day + INNER JOIN overtime + ON overtime.id = overtime_day.overtime_id + ${orderBy == 'overtimeTakeRestSum' ? `AND overtime.compensate = '调休'` : ''} + ${orderBy == 'overtimePaySum' ? `AND overtime.compensate = '发放加班补偿'` : ''} + ${overtimeDayStatisticWhere.length ? ` + WHERE ${overtimeDayStatisticWhere.join(' AND ')} + `: ''} + GROUP BY overtime.pep_user_id + ) AS overtimeDayStatistic + ON overtimeDayStatistic.pepUserId = member.pep_user_id`: ''} + + ${overtimeCountStatistic ? ` + LEFT JOIN ( + SELECT + overtime.pep_user_id AS pepUserId, + count(pep_process_story_id) AS count + FROM overtime + ${overtimeCountStatisticWhere.length ? ` + WHERE ${overtimeCountStatisticWhere.join(' AND ')} + `: ''} + GROUP BY overtime.pep_user_id + ) AS overtimeCountStatistic + ON overtimeCountStatistic.pepUserId = member.pep_user_id + `: ''} + + ${vacateDurationStatistic || + orderBy == 'vacateSum' ? + `LEFT JOIN ( + SELECT + vacate.pep_user_id AS pepUserId, + sum(vacate_day.duration) AS duration + FROM vacate_day + INNER JOIN vacate + ON vacate.id = vacate_day.vacate_id + ${vacateDayStatisticWhere.length ? ` + WHERE ${vacateDayStatisticWhere.join(' AND ')} + `: ''} + GROUP BY vacate.pep_user_id + ) AS vacateDayStatistic + ON vacateDayStatistic.pepUserId = member.pep_user_id`: ''} + + ${vacateCountStatistic ? ` + LEFT JOIN ( + SELECT + vacate.pep_user_id AS pepUserId, + count(pep_process_story_id) AS count + FROM vacate + ${vacateCountStatisticWhere.length ? ` + WHERE ${vacateCountStatisticWhere.join(' AND ')} + `: ''} + GROUP BY vacate.pep_user_id + ) AS vacateCountStatistic + ON vacateCountStatistic.pepUserId = member.pep_user_id + `: ''} + WHERE member.del = 0 ${keywordTarget == 'role' && keyword ? ` AND user.id IN ( @@ -116,7 +220,7 @@ module.exports = function (app, opts) { ` : ''} ${state == 'dimission' ? `AND member.dimission_date IS NOT null` : ''} ${state == 'onJob' ? `AND member.dimission_date IS null` : ''} - ${whereFromSelectOption.length ? `AND ${whereFromSelectOption.join('AND')}` : ''} + ${whereFromSelectOption.length && nowAttendanceTime ? `AND ${whereFromSelectOption.join('AND')}` : ''} ${hiredateStart ? ` AND member.hiredate >= '${moment(hiredateStart).format('YYYY-MM-DD')}' `: ''} @@ -146,18 +250,39 @@ module.exports = function (app, opts) { department.id AS depId FROM ( SELECT - member.*, - hrVacate.vacateStartTime AS vacateStartTime, - hrVacate.vacateEndTime AS vacateEndTime, - hrOvertime.overtimeStartTime AS overtimeStartTime, - hrOvertime.overtimeEndTime AS overtimeEndTime + ${orderBy == 'overtimeTakeRestSum' + || orderBy == 'overtimePaySum' + || orderBy == 'overtimeSum' ? ` + overtimeDayStatistic.duration AS overtimeDayStatisticDuration + `: ''} + + ${overtimeCountStatistic ? ` + overtimeCountStatistic.count AS overtimeCount, + `: ''} + + ${orderBy == 'vacateSum' || vacateDurationStatistic ? ` + vacateDayStatistic.duration AS vacateDayStatisticDuration, + `: ''} + + ${vacateCountStatistic ? ` + vacateCountStatistic.count AS vacateCount, + `: ''} + + ${nowAttendanceTime ? ` + hrVacate.vacateStartTime AS vacateStartTime, + hrVacate.vacateEndTime AS vacateEndTime, + hrOvertime.overtimeStartTime AS overtimeStartTime, + hrOvertime.overtimeEndTime AS overtimeEndTime, + `: ''} + + member.* ${innerSelectQuery} ${limit ? `LIMIT ${limit}` : ''} ${limit && page ? 'OFFSET ' + parseInt(limit) * parseInt(page) : ''} ) AS hrMember LEFT JOIN ${pepEmis}.user AS user - ON hrMember."member.pep_user_id" = user.id + ON pepUserId = user.id LEFT JOIN ${pepEmis}.user_role AS user_role ON ${pepEmis}.user_role.user = user.id LEFT JOIN ${pepEmis}.role AS role @@ -167,12 +292,30 @@ module.exports = function (app, opts) { LEFT JOIN ${pepEmis}.department AS department ON department.id = department_user.department ${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."member.overtimeDayStatisticDuration"' + : orderBy == 'overtimeCount' ? + 'hrMember.overtimeCount' + : orderBy == 'vacateSum' ? + 'hrMember.vacateDayStatisticDuration' + : orderBy == 'vacateCount' ? + 'hrMember.vacateCount' + : 'user.people_code'} + ${orderDirection || 'ASC'} `).toPromise() const countRes = await clickHouse.hr.query(` SELECT - count(member.pep_user_id) AS count - ${innerSelectQuery} + count(member.pep_user_id) AS count + ${innerSelectQuery} `).toPromise() return { @@ -181,7 +324,87 @@ module.exports = function (app, opts) { } } + async function packageUserData (userRes, option = {}) { + + const { judgeHoliday, } = app.fs.utils + let workTime = false + let dayoffTime = false + if (option.state) { + const curDay = moment().format('YYYY-MM-DD') + const nowTime = moment() + const holidayJudge = await judgeHoliday(curDay) + if (holidayJudge) { + if ( + holidayJudge.workday + && nowTime.isAfter(moment(curDay + ' 08:30')) + && nowTime.isBefore(moment(curDay + ' 17:30')) + ) { + workTime = true + } else if (holidayJudge.dayoff || holidayJudge.festivals) { + dayoffTime = true + } + } + } + + let returnD = [] + let pepUserIds = [] + userRes.rows.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 { + let obj = {} + for (let k in u) { + let nextKey = k.replace('hrMember.', '') + .replace('member.', '') + if (nextKey.includes('_')) { + nextKey = nextKey.toLowerCase() + .replace( + /(_)[a-z]/g, + (L) => L.toUpperCase() + ) + .replace(/_/g, '') + } + obj[nextKey] = u[k] == '1970-01-01 00:00:00.000000' ? null : u[k] + } + pepUserIds.push(u.pepUserId) + 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 { - memberList + memberList, + packageUserData } } \ No newline at end of file diff --git a/web/client/assets/images/hrImg/!.png b/web/client/assets/images/hrImg/!.png new file mode 100644 index 0000000..f9c334f Binary files /dev/null and b/web/client/assets/images/hrImg/!.png differ diff --git a/web/client/assets/images/hrImg/V.png b/web/client/assets/images/hrImg/V.png new file mode 100644 index 0000000..fd5a097 Binary files /dev/null and b/web/client/assets/images/hrImg/V.png differ diff --git a/web/client/assets/images/hrImg/setUp.png b/web/client/assets/images/hrImg/setUp.png new file mode 100644 index 0000000..ea73217 Binary files /dev/null and b/web/client/assets/images/hrImg/setUp.png differ diff --git a/web/client/index.ejs b/web/client/index.ejs index 54e5c56..49ff0b5 100644 --- a/web/client/index.ejs +++ b/web/client/index.ejs @@ -9,7 +9,7 @@ - +
diff --git a/web/client/index.html b/web/client/index.html index b4cac35..cba0f37 100644 --- a/web/client/index.html +++ b/web/client/index.html @@ -2,23 +2,23 @@ - - + + - + - - + + - + - - + + - - + - + - + \ No newline at end of file diff --git a/web/client/src/components/setup.jsx b/web/client/src/components/setup.jsx index c4cd070..41d8792 100644 --- a/web/client/src/components/setup.jsx +++ b/web/client/src/components/setup.jsx @@ -1,124 +1,123 @@ import React, { useState, useEffect } from "react"; import { - Modal, - CheckboxGroup, - Checkbox, + Modal, + CheckboxGroup, + Checkbox, } from "@douyinfe/semi-ui"; -function Setup(props) { - const { - close, - tableType, - tableList - } = props; +function Setup (props) { + const { + close, + tableType, + tableList, + length + } = props; + const [check, setCheck] = useState([]); - console.log(tableType, - tableList); - 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" }; - useEffect(() => { - //获取是否勾选信息 - const checkItem = localStorage.getItem(tableType); - setCheck(checkItem?JSON.parse(checkItem) : []) - ischeck(); - }, []); - function ischeck(value) { - if (check.length >= 8) { - if (check.includes(value)) { - return false; - } else { - return true; - } + useEffect(() => { + //获取是否勾选信息 + const checkItem = localStorage.getItem(tableType); + setCheck(checkItem ? JSON.parse(checkItem) : []) + ischeck(); + }, []); + function ischeck (value) { + if (check.length >= length) { + if (check.includes(value)) { + return false; + } else { + return true; + } + } } - } - return ( -