Skip to content

Commit

Permalink
feat(timeclock): export preliminary report into excel (#83)
Browse files Browse the repository at this point in the history
  • Loading branch information
nandinboldn committed Jan 19, 2023
1 parent 6e1e570 commit ae7844d
Show file tree
Hide file tree
Showing 10 changed files with 198 additions and 19 deletions.
3 changes: 2 additions & 1 deletion packages/plugin-timeclock-api/package.json
Expand Up @@ -10,6 +10,7 @@
"dependencies": {
"dayjs": "1.8.15",
"sequelize": "^6.27.0",
"tedious": "^15.1.2"
"tedious": "^15.1.2",
"xlsx-populate": "^1.20.1"
}
}
17 changes: 17 additions & 0 deletions packages/plugin-timeclock-api/src/configs.ts
Expand Up @@ -5,6 +5,8 @@ import { initBroker } from './messageBroker';
import { getSubdomain } from '@erxes/api-utils/src/core';
import { generateModels } from './connectionResolver';
import cronjobs from './cronjobs/timelock';
import { routeErrorHandling } from '@erxes/api-utils/src/requests';
import { buildFile } from './reportExport';

export let mainDb;
export let debug;
Expand Down Expand Up @@ -40,6 +42,21 @@ export default {
mainDb = options.db;
const app = options.app;

app.get(
'/report-export',
routeErrorHandling(async (req: any, res) => {
const { query } = req;
const subdomain = getSubdomain(req);
const models = await generateModels(subdomain);

const result = await buildFile(models, subdomain, query);

res.attachment(`${result.name}.xlsx`);

return res.send(result.response);
})
);

initBroker(options.messageBrokerClient);

graphqlPubsub = options.pubsubClient;
Expand Down
11 changes: 11 additions & 0 deletions packages/plugin-timeclock-api/src/constants.ts
@@ -0,0 +1,11 @@
export const PRELIMINARY_REPORT_COLUMNS = [
'№ ',
'ID',
'Овог',
'Нэр',
'Хэлтэс',
'Албан тушаал',
'Ажиллавал зохих хоног',
'Ажилласан хоног',
'Тайлбар'
];
19 changes: 13 additions & 6 deletions packages/plugin-timeclock-api/src/graphql/resolvers/queries.ts
Expand Up @@ -28,7 +28,11 @@ const timeclockQueries = {
const queryList = models.Timeclocks.find(selector);

const list = paginate(
models.Timeclocks.find(selector).sort({ userId: 1, employeeId: 1 }),
models.Timeclocks.find(selector).sort({
userId: 1,
shiftStart: 1,
shfitEnd: 1
}),
{
perPage: queryParams.perPage,
page: queryParams.page
Expand Down Expand Up @@ -58,10 +62,13 @@ const timeclockQueries = {
const selector = await generateFilter(queryParams, subdomain, 'absence');
const totalCount = models.Absences.find(selector).countDocuments();

const list = paginate(models.Absences.find(selector).sort({ userId: 1 }), {
perPage: queryParams.perPage,
page: queryParams.page
});
const list = paginate(
models.Absences.find(selector).sort({ userId: 1, startTime: 1 }),
{
perPage: queryParams.perPage,
page: queryParams.page
}
);

return { list, totalCount };
},
Expand Down Expand Up @@ -121,7 +128,7 @@ const timeclockQueries = {
const userBranches = await findBranches(subdomain, teamMemberId);

finalReport.push({
groupTitle: userBranches[0].title,
groupTitle: userBranches.length && userBranches[0].title,
groupReport: [userReport]
});
}
Expand Down
6 changes: 6 additions & 0 deletions packages/plugin-timeclock-api/src/graphql/resolvers/utils.ts
Expand Up @@ -281,6 +281,12 @@ export const timeclockReportByUser = async (
$gte: fixDate(startTime),
$lte: fixDate(endTime)
}
},
{
shiftEnd: {
$gte: fixDate(startTime),
$lte: fixDate(endTime)
}
}
]
});
Expand Down
121 changes: 121 additions & 0 deletions packages/plugin-timeclock-api/src/reportExport.ts
@@ -0,0 +1,121 @@
import { IColumnLabel } from '@erxes/api-utils/src';
import * as moment from 'moment';
import * as xlsxPopulate from 'xlsx-populate';
import { IModels } from './connectionResolver';
import { PRELIMINARY_REPORT_COLUMNS } from './constants';
import { findBranches, timeclockReportByUser } from './graphql/resolvers/utils';
import { findAllTeamMembersWithEmpId, generateCommonUserIds } from './utils';
/**
* Creates blank workbook
*/
export const createXlsFile = async () => {
// Generating blank workbook
const workbook = await xlsxPopulate.fromBlankAsync();

return { workbook, sheet: workbook.sheet(0) };
};

/**
* Generates downloadable xls file on the url
*/
export const generateXlsx = async (workbook: any): Promise<string> => {
return workbook.outputAsync();
};

export const buildFile = async (
models: IModels,
subdomain: string,
query: any
) => {
const reportType = query.reportType;
const userIds =
query.userIds instanceof Array || !query.userIds
? query.userIds
: [query.userIds];

const branchIds =
query.branchIds instanceof Array || !query.branchIds
? query.branchIds
: [query.branchIds];
const departmentIds =
query.departmentIds instanceof Array || !query.departmentIds
? query.departmentIds
: [query.departmentIds];

const startDate = query.startDate;
const endDate = query.endDate;

const { workbook, sheet } = await createXlsFile();

const addRow = async (values: string[][], rowIndex: number) => {
const r = sheet.range(`A${rowIndex}:I${rowIndex}`);
r.value(values);
};

const table_headers = PRELIMINARY_REPORT_COLUMNS;
const teamMembersWithEmpId = await findAllTeamMembersWithEmpId(subdomain);
const teamMembersObject = {};
const teamEmployeeIds: string[] = [];

for (const teamMember of teamMembersWithEmpId) {
if (!teamMember.employeeId) {
continue;
}

teamMembersObject[teamMember._id] = {
employeeId: teamMember.employeeId,
firstName: teamMember.details.firstName,
lastName: teamMember.details.lastName,
postion: teamMember.details.postion
};
teamEmployeeIds.push(teamMember._id);
}
const teamMemberIdsFromFilter = await generateCommonUserIds(
subdomain,
userIds,
branchIds,
departmentIds
);

const totalTeamMemberIds = teamMemberIdsFromFilter.length
? teamMemberIdsFromFilter
: teamEmployeeIds;

addRow([table_headers], 1);

let rowIdx = 2;

const addUserInfoToRow = async (teamMemberId: string) => {
const userReport = await timeclockReportByUser(
teamMemberId,
subdomain,
startDate,
endDate
);
const userBranches = await findBranches(subdomain, teamMemberId);

const memberValues = [
(rowIdx - 1).toString(),
teamMembersObject[teamMemberId].employeeId || '',
teamMembersObject[teamMemberId].lastName || '',
teamMembersObject[teamMemberId].firstName || '',
userBranches.length ? userBranches[0].title : '',
teamMembersObject[teamMemberId].postion || '',
userReport.totalDaysScheduledThisMonth?.toString() || '',
userReport.totalDaysWorkedThisMonth?.toString() || ''
];

await addRow([memberValues], rowIdx);
rowIdx += 1;
};

// tslint:disable-next-line:prefer-for-of
for (const teamMemberId of totalTeamMemberIds) {
await addUserInfoToRow(teamMemberId);
}

return {
name: `${reportType}-report-${moment().format('YYYY-MM-DD')}`,
response: await generateXlsx(workbook)
};
};
1 change: 0 additions & 1 deletion packages/plugin-timeclock-api/src/utils.ts
Expand Up @@ -71,7 +71,6 @@ const connectAndQueryFromMySql = async (
if (!teamMember.employeeId) {
continue;
}

teamMembersObject[teamMember.employeeId] = teamMember._id;
teamEmployeeIds.push(teamMember.employeeId);
}
Expand Down
Expand Up @@ -15,13 +15,12 @@ type Props = {
history: any;
reports: IReport[];
getActionBar: (actionBar: any) => void;
exportReport: () => void;
};

function ReportList(props: Props) {
const { history, reports, getActionBar } = props;
const [selectedType, setType] = useState(
localStorage.getItem('displayType') || ''
);
const { history, reports, queryParams, getActionBar, exportReport } = props;
const [selectedType, setType] = useState(queryParams.reportType);
const content = (
<Table>
<thead>
Expand Down Expand Up @@ -77,7 +76,7 @@ function ReportList(props: Props) {
const renderExportBtn = () => {
return (
<div>
<Button>Export</Button>
<Button onClick={exportReport}>Export</Button>
</div>
);
};
Expand Down
11 changes: 6 additions & 5 deletions packages/plugin-timeclock-ui/src/components/sidebar/SideBar.tsx
Expand Up @@ -48,11 +48,13 @@ const LeftSideBar = (props: Props) => {
};

const setParams = (key: string, value: any) => {
router.setParams(history, {
[key]: value
});
if (value) {
router.setParams(history, {
[key]: value
});

removePageParams();
removePageParams();
}
};

const renderBranchOptions = (branches: any[]) => {
Expand Down Expand Up @@ -89,7 +91,6 @@ const LeftSideBar = (props: Props) => {

const onStartDateChange = date => {
setStartDate(date);

setParams('startDate', date);
};

Expand Down
@@ -1,11 +1,13 @@
import { withProps } from '@erxes/ui/src/utils/core';
import { getEnv, withProps } from '@erxes/ui/src/utils/core';
import queryString from 'query-string';
import * as compose from 'lodash.flowright';
import { graphql } from 'react-apollo';
import gql from 'graphql-tag';
import React from 'react';
import ReportList from '../../components/report/ReportList';
import { queries } from '../../graphql';
import { BranchesQueryResponse, ReportsQueryResponse } from '../../types';
import Spinner from '@erxes/ui/src/components/Spinner';
import { generateParams } from '../../utils';

type Props = {
Expand All @@ -28,9 +30,24 @@ const ListContainer = (props: FinalProps) => {
const { listReportsQuery, queryParams, getActionBar, showSideBar } = props;
const { branchId, deptId } = queryParams;

if (listReportsQuery.loading) {
return <Spinner />;
}
const exportReport = () => {
const stringified = queryString.stringify({
...queryParams
});

const { REACT_APP_API_URL } = getEnv();
window.open(
`${REACT_APP_API_URL}/pl:timeclock/report-export?${stringified}`
);
};

const updatedProps = {
...props,
getActionBar,
exportReport,
reports: listReportsQuery.timeclockReports || [],
branchId,
deptId
Expand Down

0 comments on commit ae7844d

Please sign in to comment.