Browse Source

加班数据统计接口

master
巴林闲侠 2 years ago
parent
commit
97c1f52f27
  1. 132
      api/app/lib/controllers/attendance/index.js
  2. 28
      api/app/lib/controllers/member/index.js
  3. 418
      api/app/lib/models/member.js
  4. 8
      api/app/lib/routes/attendance/index.js
  5. 46
      api/app/lib/utils/member.js

132
api/app/lib/controllers/attendance/index.js

@ -0,0 +1,132 @@
'use strict';
async function overtimeStatistic (ctx) {
try {
const { models } = ctx.fs.dc;
const { clickHouse } = ctx.app.fs
const { judgeHoliday, memberList } = 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
})
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]
}
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
}] : [],
del: undefined,
pepuserid: undefined,
"vacateStartTime": undefined,
"vacateEndTime": undefined,
"overtimeStartTime": undefined,
"overtimeEndTime": undefined,
})
}
})
// 查加班总数
const countRes = await clickHouse.hr.query(`
SELECT
overtime.pep_user_id AS pepUserId,
count(pep_process_story_id) AS count
FROM overtime
WHERE overtime.pep_user_id IN (${pepUserIds.join(',')})
${startDate ? `
AND overtime.start_time >= '${moment(startDate).format('YYYY-MM-DD')}'
`: ''}
${endDate ? `
AND overtime.start_time <= '${moment(endDate).format('YYYY-MM-DD')}'
` : ''}
GROUP BY overtime.pep_user_id
`).toPromise()
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()
returnD.forEach(u => {
u.overtimeCount = (countRes.find(c => c.pepUserId == u.pepUserId) || {}).count || 0
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
}
}
}
module.exports = {
overtimeStatistic,
};

28
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
@ -270,7 +270,8 @@ async function list (ctx) {
const { judgeHoliday, memberList } = 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')
@ -291,7 +292,8 @@ async function list (ctx) {
}
const userRes = await memberList({
keywordTarget, keyword, limit, page, state,
hiredateStart, hiredateEnd, marital, native, workPlace
hiredateStart, hiredateEnd, marital, native, workPlace,
orderBy, orderDirection,
})
let returnD = []
@ -351,7 +353,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
@ -440,7 +442,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
@ -520,7 +522,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
@ -668,7 +670,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

418
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;
};

8
api/app/lib/routes/attendance/index.js

@ -0,0 +1,8 @@
'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);
};

46
api/app/lib/utils/member.js

@ -6,7 +6,9 @@ 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,
overtimeDayStatisticStartDate, overtimeDayStatisticendDate
}) {
const { judgeHoliday } = app.fs.utils
const { clickHouse } = app.fs
@ -58,12 +60,20 @@ module.exports = function (app, opts) {
}
}
let overtimeDayStatisticWhere = []
if (overtimeDayStatisticStartDate) {
overtimeDayStatisticWhere.push(`overtime_day.day >= '${moment(startDate).format('YYYY-MM-DD')}'`)
}
if (overtimeDayStatisticendDate) {
overtimeDayStatisticWhere.push(`overtime_day.day <= '${moment(endDate).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}%'
@ -94,6 +104,23 @@ module.exports = function (app, opts) {
) 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`: ''}
WHERE
member.del = 0
${keywordTarget == 'role' && keyword ? `
@ -147,6 +174,11 @@ module.exports = function (app, opts) {
FROM (
SELECT
member.*,
${orderBy == 'overtimeTakeRestSum' || orderBy == 'overtimePaySum' || orderBy == 'overtimeSum' ? `
overtimeDayStatistic.duration AS overtimeDayStatisticDuration
`: ''}
hrVacate.vacateStartTime AS vacateStartTime,
hrVacate.vacateEndTime AS vacateEndTime,
hrOvertime.overtimeStartTime AS overtimeStartTime,
@ -167,12 +199,18 @@ 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"'
: '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 {

Loading…
Cancel
Save