From 51d0cc6dd3c975886d2705de195b0e4248ff1dcf Mon Sep 17 00:00:00 2001 From: "gao.zhiyuan" Date: Sat, 15 Oct 2022 16:35:14 +0800 Subject: [PATCH] =?UTF-8?q?=E4=B8=AA=E4=BA=BA=E5=8A=A0=E7=8F=AD=E7=BB=9F?= =?UTF-8?q?=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_',] ,长度较长的 前缀放前面