From 51d0cc6dd3c975886d2705de195b0e4248ff1dcf Mon Sep 17 00:00:00 2001 From: "gao.zhiyuan" Date: Sat, 15 Oct 2022 16:35:14 +0800 Subject: [PATCH 1/4] =?UTF-8?q?=E4=B8=AA=E4=BA=BA=E5=8A=A0=E7=8F=AD?= =?UTF-8?q?=E7=BB=9F=E8=AE=A1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- api/app/lib/controllers/member/index.js | 148 +++++++++++++++++++++++- api/app/lib/models/overtime.js | 9 ++ api/app/lib/models/vacate.js | 9 ++ api/app/lib/routes/member/index.js | 6 + api/app/lib/schedule/attendance.js | 45 ++++--- api/sequelize-automate.config.js | 2 +- 6 files changed, 202 insertions(+), 17 deletions(-) diff --git a/api/app/lib/controllers/member/index.js b/api/app/lib/controllers/member/index.js index b04c495..878fd09 100644 --- a/api/app/lib/controllers/member/index.js +++ b/api/app/lib/controllers/member/index.js @@ -31,7 +31,7 @@ async function add (ctx) { } }) } else { - await models.create(storageData) + await models.Member.create(storageData) } ctx.status = 204; @@ -384,10 +384,156 @@ async function list (ctx) { } } +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).format('YYYY-MM-DD HH:mm:ss')}' + AND wpStory.create_at > '${moment(startDate).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, + 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() + + let returnD = { + ...(statisticRes.length ? statisticRes[0] : {}), + data: dataRes + } + 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 && 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')}'` + ) + } + 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() + + let returnD = { + statistic: statisticRes, + data: dataRes + } + 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; + + ctx.status = 20; + } catch (error) { + 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, }; \ No newline at end of file diff --git a/api/app/lib/models/overtime.js b/api/app/lib/models/overtime.js index 61c6787..4637dd8 100644 --- a/api/app/lib/models/overtime.js +++ b/api/app/lib/models/overtime.js @@ -131,6 +131,15 @@ module.exports = dc => { primaryKey: false, field: "compensate", autoIncrement: false + }, + reason: { + type: DataTypes.STRING, + allowNull: true, + defaultValue: null, + comment: "原因", + primaryKey: false, + field: "reason", + autoIncrement: false } }, { tableName: "overtime", diff --git a/api/app/lib/models/vacate.js b/api/app/lib/models/vacate.js index 7a89132..f5fbe66 100644 --- a/api/app/lib/models/vacate.js +++ b/api/app/lib/models/vacate.js @@ -77,6 +77,15 @@ module.exports = dc => { primaryKey: false, field: "wf_process_state", autoIncrement: false + }, + reason: { + type: DataTypes.STRING, + allowNull: true, + defaultValue: null, + comment: "原因", + primaryKey: false, + field: "reason", + autoIncrement: false } }, { tableName: "vacate", diff --git a/api/app/lib/routes/member/index.js b/api/app/lib/routes/member/index.js index a3f5d12..b288957 100644 --- a/api/app/lib/routes/member/index.js +++ b/api/app/lib/routes/member/index.js @@ -17,4 +17,10 @@ module.exports = function (app, router, opts) { app.fs.api.logAttr['GET/member/list'] = { content: '查询人员列表', visible: true }; router.get('/member/list', member.list); + + app.fs.api.logAttr['GET/member/overtime'] = { content: '查询单个人员加班统计数据', visible: true }; + router.get('/member/overtime', member.overTimeStatistics); + + app.fs.api.logAttr['GET/member/vacate'] = { content: '查询单个人员请假统计数据', visible: true }; + router.get('/member/vacate', member.vacateStatistics); }; \ No newline at end of file diff --git a/api/app/lib/schedule/attendance.js b/api/app/lib/schedule/attendance.js index 7e72c81..8a1c20d 100644 --- a/api/app/lib/schedule/attendance.js +++ b/api/app/lib/schedule/attendance.js @@ -5,8 +5,8 @@ module.exports = function (app, opts) { const updateAttendance = app.fs.scheduleInit( // 妥妥流水账 (*^▽^*) { - interval: '34 21 4 * * *', - // interval: '*/3 * * * *', + // interval: '34 21 4 * * *', + interval: '34 21 */1 * * *', // immediate: true, // proRun: true, }, @@ -22,6 +22,9 @@ module.exports = function (app, opts) { const { } = app.fs.utils let overtimeNeedData = { + reason: { + keyWord: ['加班事由'] + }, begainTime: { keyWord: ['加班开始时间'], require: true, @@ -44,6 +47,9 @@ module.exports = function (app, opts) { } let vacateNeedData = { + reason: { + keyWord: ['请假事由'] + }, begainTime: { keyWord: ['请假开始时间', '请假起始时间'], keys: ['leaveStartTime'], @@ -138,7 +144,7 @@ module.exports = function (app, opts) { if (needData[nd].noProcess) { continue } - if (applyDetail.formSchema) { + if (applyDetail.formSchema) { const { jsonSchema } = JSON.parse(applyDetail.formSchema) needData[nd].schemaPath = schemaRecursionObj({ jsonSchema: jsonSchema || {}, @@ -160,7 +166,7 @@ module.exports = function (app, opts) { ) } else { console.warn( - `表单数据缺失:`, + `表单数据缺失:[${nd}]`, applyDetail ); } @@ -177,6 +183,8 @@ module.exports = function (app, opts) { } } + const existOvertimeCount = await models.Overtime.count() + const existVacateCount = await models.Vacate.count() const attendanceRes = await clickHouse.pepEmis.query( ` @@ -207,6 +215,11 @@ module.exports = function (app, opts) { AND fgroup.name = '假勤管理' INNER JOIN ${camWorkflow}.act_hi_procinst AS procin ON procin.id_ = story.procinst_id + ` + + ` + ${existOvertimeCount || existVacateCount ? + `WHERE story.create_at > '${moment().subtract(1, 'month').format('YYYY-MM-DD HH:mm:ss')}'` + : ''} ` ).toPromise() @@ -218,7 +231,7 @@ module.exports = function (app, opts) { if (a.processName.indexOf('请假') > -1) { let needData = JSON.parse(JSON.stringify(vacateNeedData)) getData(a, needData) - const { begainTime, endTime, type, hrAffirmType, duration, hrAffirmDuration } = needData + const { begainTime, endTime, type, hrAffirmType, duration, hrAffirmDuration, reason } = needData if (begainTime.value && endTime.value && type.value) { let durationSec = 0 if (hrAffirmDuration.value) { @@ -253,6 +266,7 @@ module.exports = function (app, opts) { duration: durationSec, type: typeStorage, wfProcessState: a.state, + reason: reason.value } if (existRes) { await models.Vacate.update(storageD, { @@ -277,7 +291,7 @@ module.exports = function (app, opts) { // 获取表单里的数据并插入 needData getData(a, needData) - const { begainTime, endTime, duration, compensate, hrAffirmDuration } = needData + const { begainTime, endTime, duration, compensate, hrAffirmDuration, reason } = needData if (begainTime.value && endTime.value && hrAffirmDuration.value && compensate.value) { let durationSec = parseFloat(hrAffirmDuration.value) * 3600 @@ -372,7 +386,8 @@ module.exports = function (app, opts) { payWorkday, payDayoff, payFestivals, - compensate: compensate.value + compensate: compensate.value, + reason: reason.value } if (existRes) { await models.Overtime.update(storageD, { @@ -410,17 +425,17 @@ module.exports = function (app, opts) { console.info(` 假勤数据更新 用时 ${moment().diff(startTime, 'seconds')} s - `) + `) console.info(` - 共:${attendanceRes.length}; - 新增:${insertCount}; - 更新数据:${updateCount}; - 非完成状态流程:${unCompletedCount}; - 不明流程:${unknowCount}; - 无效(warning):${invalidCount}; + 共:${attendanceRes.length}; + 新增:${insertCount}; + 更新数据:${updateCount}; + 非完成状态流程:${unCompletedCount}; + 不明流程:${unknowCount}; + 无效(warning):${invalidCount}; `); } catch (error) { - app.fs.logger.error(`sechedule: updateAttendance, error: ${error}`); + app.fs.logger.error(`sechedule: updateAttendance, error: ${error} `); } }); return { diff --git a/api/sequelize-automate.config.js b/api/sequelize-automate.config.js index 3c6744b..e6e9122 100644 --- a/api/sequelize-automate.config.js +++ b/api/sequelize-automate.config.js @@ -26,7 +26,7 @@ module.exports = { dir: './app/lib/models', // 指定输出 models 文件的目录 typesDir: 'models', // 指定输出 TypeScript 类型定义的文件目录,只有 TypeScript / Midway 等会有类型定义 emptyDir: false, // !!! 谨慎操作 生成 models 之前是否清空 `dir` 以及 `typesDir` - tables: ['member'], // 指定生成哪些表的 models,如 ['user', 'user_post'];如果为 null,则忽略改属性 + tables: ['overtime','vacate'], // 指定生成哪些表的 models,如 ['user', 'user_post'];如果为 null,则忽略改属性 skipTables: [], // 指定跳过哪些表的 models,如 ['user'];如果为 null,则忽略改属性 tsNoCheck: false, // 是否添加 `@ts-nocheck` 注释到 models 文件中 ignorePrefix: [], // 生成的模型名称忽略的前缀,因为 项目中有以下表名是以 t_ 开头的,在实际模型中不需要, 可以添加多个 [ 't_data_', 't_',] ,长度较长的 前缀放前面 From eb4b295a9fa252ec0b1c9f2f810866981b56a8e7 Mon Sep 17 00:00:00 2001 From: "gao.zhiyuan" Date: Sat, 15 Oct 2022 16:40:30 +0800 Subject: [PATCH 2/4] =?UTF-8?q?=E5=90=88=E5=B9=B6=E4=BB=A3=E7=A0=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- api/app/lib/controllers/member/index.js | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/api/app/lib/controllers/member/index.js b/api/app/lib/controllers/member/index.js index 6c6d70b..a760e39 100644 --- a/api/app/lib/controllers/member/index.js +++ b/api/app/lib/controllers/member/index.js @@ -1,7 +1,7 @@ 'use strict'; const moment = require('moment') -async function add(ctx) { +async function add (ctx) { try { const { models } = ctx.fs.dc; const { @@ -44,7 +44,7 @@ async function add(ctx) { } } -async function edit(ctx) { +async function edit (ctx) { try { const { models } = ctx.fs.dc; const { @@ -84,7 +84,7 @@ async function edit(ctx) { } } -async function searchPepMember(ctx) { +async function searchPepMember (ctx) { try { const { models } = ctx.fs.dc; const { clickHouse } = ctx.app.fs @@ -160,7 +160,7 @@ async function searchPepMember(ctx) { } } -async function del(ctx) { +async function del (ctx) { try { const { models } = ctx.fs.dc; const { pepUserId } = ctx.query @@ -183,7 +183,7 @@ async function del(ctx) { } } -async function list(ctx) { +async function list (ctx) { try { const { models } = ctx.fs.dc; const { judgeHoliday } = ctx.app.fs.utils @@ -524,7 +524,10 @@ async function exportData (ctx) { ctx.body = { message: typeof error == 'string' ? error : undefined } -async function addMembersBulk(ctx) { + } +} + +async function addMembersBulk (ctx) { let errorMsg = { message: '批量添加员工信息失败' }; const transaction = await ctx.fs.dc.orm.transaction(); try { @@ -601,4 +604,4 @@ module.exports = { vacateStatistics, exportData, addMembersBulk -}; \ No newline at end of file +} \ No newline at end of file From 8b7f1d5aa81b0536aa8399e82eb3d394e12aff46 Mon Sep 17 00:00:00 2001 From: "gao.zhiyuan" Date: Sat, 15 Oct 2022 17:29:26 +0800 Subject: [PATCH 3/4] =?UTF-8?q?=E6=9B=B4=E6=94=B9=E5=AD=97=E6=AE=B5?= =?UTF-8?q?=E5=B0=8F=E9=A9=BC=E5=B3=B0=E8=BF=94=E5=9B=9E?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- api/app/lib/controllers/member/index.js | 162 +++--------------------- api/app/lib/utils/member.js | 147 +++++++++++++++++++++ 2 files changed, 164 insertions(+), 145 deletions(-) create mode 100644 api/app/lib/utils/member.js diff --git a/api/app/lib/controllers/member/index.js b/api/app/lib/controllers/member/index.js index a760e39..59bf603 100644 --- a/api/app/lib/controllers/member/index.js +++ b/api/app/lib/controllers/member/index.js @@ -186,145 +186,10 @@ async function del (ctx) { async function list (ctx) { try { const { models } = ctx.fs.dc; - const { judgeHoliday } = ctx.app.fs.utils - const { clickHouse } = ctx.app.fs - const { database: pepEmis } = clickHouse.pepEmis.opts.config + const { judgeHoliday, memberList } = ctx.app.fs.utils const { keywordTarget, keyword, limit, page, state } = ctx.query - ctx.status = 200; - - const curDay = moment().format('YYYY-MM-DD') - const nowTime = moment() - let whereOption = [] - let whereFromSelectOption = [] - if (state == 'inOffice') { - // 在岗 - const holidayJudge = await judgeHoliday(curDay) - if (holidayJudge) { - if ( - holidayJudge.workday - && nowTime.isAfter(moment(curDay + ' 08:30')) - && nowTime.isBefore(moment(curDay + ' 17:30')) - ) { - // 在工作日的工作时间范围 无请假记录 - whereFromSelectOption.push(`vacateStartTime IS NULL`) - } else { - ctx.body = [] - return - } - } else { - ctx.body = [] - return - } - } - if (state == 'dayoff') { - // 放假 - const holidayJudge = await judgeHoliday(curDay) - if (holidayJudge) { - if ( - holidayJudge.dayoff || holidayJudge.festivals - ) { - // 在休息日范围内且无加班申请 - whereFromSelectOption.push(`overtimeStartTime IS NULL`) - } else { - ctx.body = [] - return - } - } else { - ctx.body = [] - return - } - } - const userRes = await clickHouse.hr.query(` - SELECT - hrMember."member.pep_user_id" AS pepUserId, - hrMember.*, - user.name AS userName, - role.name AS roleName, - role.id AS roleId, - department.name AS depName, - department.id AS depId - FROM ( - SELECT - member.*, - hrVacate.vacateStartTime AS vacateStartTime, - hrVacate.vacateEndTime AS vacateEndTime, - hrOvertime.overtimeStartTime AS overtimeStartTime, - hrOvertime.overtimeEndTime AS overtimeEndTime - FROM member - INNER JOIN ${pepEmis}.user AS user - ON member.pep_user_id = user.id - ${keywordTarget == 'number' && keyword ? ` - AND user.id 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 - member.pep_user_id > 0 - ${keywordTarget == 'role' && keyword ? ` - AND user.id IN ( - SELECT user_role.user - FROM ${pepEmis}.user_role AS user_role - INNER JOIN ${pepEmis}.role AS role - ON role.id = user_role.role - AND role.name LIKE '%${keyword}%' - ) - ` : ''} - ${keywordTarget == 'dep' && keyword ? ` - AND user.id IN ( - SELECT department_user.user - FROM ${pepEmis}.department_user AS department_user - INNER JOIN ${pepEmis}.department AS department - ON department.id = department_user.department - AND department.name LIKE '%${keyword}%' - ) - ` : ''} - ${state == 'dimission' ? `AND member.dimission_date IS NOT null` : ''} - ${state == 'onJob' ? `AND member.dimission_date IS null` : ''} - ${whereFromSelectOption.length ? `AND ${whereFromSelectOption.join('AND')}` : ''} - ${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 - LEFT JOIN ${pepEmis}.user_role AS user_role - ON ${pepEmis}.user_role.user = user.id - LEFT JOIN ${pepEmis}.role AS role - ON ${pepEmis}.role.id = user_role.role - 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 - ${whereOption.length ? `WHERE ${whereOption.join(' AND ')}` : ''} - `).toPromise() + const userRes = await memberList({ keywordTarget, keyword, limit, page, state }) let returnD = [] userRes.forEach(u => { @@ -345,17 +210,17 @@ async function list (ctx) { } else { let obj = {} for (let k in u) { - obj[ - k.replace('hrMember.', '') - .replace('member.', '') - // 变为小驼峰 - .toLowerCase() + let nextKey = k.replace('hrMember.', '') + .replace('member.', '') + if (nextKey.includes('_')) { + nextKey = nextKey.toLowerCase() .replace( /(_)[a-z]/g, (L) => L.toUpperCase() ) .replace(/_/g, '') - ] = u[k] + } + obj[nextKey] = u[k] } returnD.push({ ...obj, @@ -373,7 +238,7 @@ async function list (ctx) { } }) - + ctx.status = 200; ctx.body = returnD } catch (error) { ctx.fs.logger.error(`path: ${ctx.path}, error: error`); @@ -516,8 +381,15 @@ async function vacateStatistics (ctx) { async function exportData (ctx) { try { const { models } = ctx.fs.dc; + const { judgeHoliday, memberList } = ctx.app.fs.utils + const { keywordTarget, keyword, limit, page, state } = ctx.query + + const userRes = await memberList({ keywordTarget, keyword, limit, page, state }) + + const tableAttributes = models['Member'].tableAttributes + + ctx.status = 200; - ctx.status = 20; } catch (error) { ctx.fs.logger.error(`path: ${ctx.path}, error: error`); ctx.status = 400; diff --git a/api/app/lib/utils/member.js b/api/app/lib/utils/member.js new file mode 100644 index 0000000..2559d9e --- /dev/null +++ b/api/app/lib/utils/member.js @@ -0,0 +1,147 @@ +'use strict'; +const moment = require('moment') +const request = require('superagent'); + +module.exports = function (app, opts) { + + async function memberList ({ keywordTarget, keyword, limit, page, state }) { + const { judgeHoliday } = app.fs.utils + const { clickHouse } = app.fs + const { database: pepEmis } = clickHouse.pepEmis.opts.config + + const curDay = moment().format('YYYY-MM-DD') + const nowTime = moment() + let whereOption = [] + let whereFromSelectOption = [] + if (state == 'inOffice') { + // 在岗 + const holidayJudge = await judgeHoliday(curDay) + if (holidayJudge) { + if ( + holidayJudge.workday + && nowTime.isAfter(moment(curDay + ' 08:30')) + && nowTime.isBefore(moment(curDay + ' 17:30')) + ) { + // 在工作日的工作时间范围 无请假记录 + whereFromSelectOption.push(`vacateStartTime IS NULL`) + } else { + return [] + } + } else { + return [] + } + } + if (state == 'dayoff') { + // 放假 + const holidayJudge = await judgeHoliday(curDay) + if (holidayJudge) { + if ( + holidayJudge.dayoff || holidayJudge.festivals + ) { + // 在休息日范围内且无加班申请 + whereFromSelectOption.push(`overtimeStartTime IS NULL`) + } else { + return [] + } + } else { + return [] + } + } + const userRes = await clickHouse.hr.query(` + SELECT + hrMember."member.pep_user_id" AS pepUserId, + hrMember.*, + user.name AS userName, + role.name AS roleName, + role.id AS roleId, + department.name AS depName, + department.id AS depId + FROM ( + SELECT + member.*, + hrVacate.vacateStartTime AS vacateStartTime, + hrVacate.vacateEndTime AS vacateEndTime, + hrOvertime.overtimeStartTime AS overtimeStartTime, + hrOvertime.overtimeEndTime AS overtimeEndTime + FROM member + INNER JOIN ${pepEmis}.user AS user + ON member.pep_user_id = user.id + ${keywordTarget == 'number' && keyword ? ` + AND user.id 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 + member.pep_user_id > 0 + ${keywordTarget == 'role' && keyword ? ` + AND user.id IN ( + SELECT user_role.user + FROM ${pepEmis}.user_role AS user_role + INNER JOIN ${pepEmis}.role AS role + ON role.id = user_role.role + AND role.name LIKE '%${keyword}%' + ) + ` : ''} + ${keywordTarget == 'dep' && keyword ? ` + AND user.id IN ( + SELECT department_user.user + FROM ${pepEmis}.department_user AS department_user + INNER JOIN ${pepEmis}.department AS department + ON department.id = department_user.department + AND department.name LIKE '%${keyword}%' + ) + ` : ''} + ${state == 'dimission' ? `AND member.dimission_date IS NOT null` : ''} + ${state == 'onJob' ? `AND member.dimission_date IS null` : ''} + ${whereFromSelectOption.length ? `AND ${whereFromSelectOption.join('AND')}` : ''} + ${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 + LEFT JOIN ${pepEmis}.user_role AS user_role + ON ${pepEmis}.user_role.user = user.id + LEFT JOIN ${pepEmis}.role AS role + ON ${pepEmis}.role.id = user_role.role + 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 + ${whereOption.length ? `WHERE ${whereOption.join(' AND ')}` : ''} + `).toPromise() + + return userRes + } + + return { + memberList + } +} \ No newline at end of file From f56e5ddb2f3c475111099107170ab286662c1b85 Mon Sep 17 00:00:00 2001 From: "gao.zhiyuan" Date: Sat, 15 Oct 2022 18:21:17 +0800 Subject: [PATCH 4/4] =?UTF-8?q?=E4=BA=BA=E5=91=98=E4=BF=A1=E6=81=AF?= =?UTF-8?q?=E5=AF=BC=E5=87=BA=2040%?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- api/app/lib/controllers/member/index.js | 81 ++++++++++++++++++++++++- api/app/lib/routes/member/index.js | 3 + api/app/lib/utils/member.js | 4 +- 3 files changed, 84 insertions(+), 4 deletions(-) diff --git a/api/app/lib/controllers/member/index.js b/api/app/lib/controllers/member/index.js index 59bf603..1b04d2d 100644 --- a/api/app/lib/controllers/member/index.js +++ b/api/app/lib/controllers/member/index.js @@ -1,5 +1,6 @@ 'use strict'; const moment = require('moment') +const fs = require('fs'); async function add (ctx) { try { @@ -381,15 +382,89 @@ async function vacateStatistics (ctx) { async function exportData (ctx) { try { const { models } = ctx.fs.dc; - const { judgeHoliday, memberList } = ctx.app.fs.utils - const { keywordTarget, keyword, limit, page, state } = ctx.query + const { simpleExcelDown, memberList } = ctx.app.fs.utils + const { keywordTarget, keyword, limit, page, state, keys = '' } = ctx.query const userRes = await memberList({ keywordTarget, keyword, limit, page, state }) const tableAttributes = models['Member'].tableAttributes + const optionKeys = keys.split(',') - ctx.status = 200; + let exportD = [] + userRes.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] + } + 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 preHeader = [{ + title: '姓名', + key: 'userName', + }] + let header = [].concat(preHeader) + for (let k in tableAttributes) { + const comment = tableAttributes[k].comment + if (k != 'id' && comment) { + if ([].includes(k)) { + // 截住不想导出的字段 + continue + } + header.push({ + title: comment || '-', + key: k, + // index: tableAttributes[k].index, + }) + } + } + + 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; diff --git a/api/app/lib/routes/member/index.js b/api/app/lib/routes/member/index.js index 2d84b3b..6bd9b0f 100644 --- a/api/app/lib/routes/member/index.js +++ b/api/app/lib/routes/member/index.js @@ -26,4 +26,7 @@ module.exports = function (app, router, opts) { app.fs.api.logAttr['POST/add/members/bulk'] = { content: '导入员工信息', visible: true }; router.post('/add/members/bulk', member.addMembersBulk); + + app.fs.api.logAttr['GET/members/export'] = { content: '导出员工信息', visible: true }; + router.get('/members/export', member.exportData); }; \ No newline at end of file diff --git a/api/app/lib/utils/member.js b/api/app/lib/utils/member.js index 2559d9e..f5b2806 100644 --- a/api/app/lib/utils/member.js +++ b/api/app/lib/utils/member.js @@ -99,7 +99,7 @@ module.exports = function (app, opts) { ON hrOvertime.pep_user_id = member.pep_user_id WHERE - member.pep_user_id > 0 + member.del = 0 ${keywordTarget == 'role' && keyword ? ` AND user.id IN ( SELECT user_role.user @@ -138,6 +138,8 @@ module.exports = function (app, opts) { ${whereOption.length ? `WHERE ${whereOption.join(' AND ')}` : ''} `).toPromise() + + return userRes }