Member Practice Summary Report and bugfixes
This commit is contained in:
12890
client/package-lock.json
generated
Normal file
12890
client/package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
@@ -67,7 +67,7 @@ const SingleIncidentsTable = props => {
|
|||||||
switch (props.column.id) {
|
switch (props.column.id) {
|
||||||
case 'memberName':
|
case 'memberName':
|
||||||
const memberId = props.row['_original'].memberId;
|
const memberId = props.row['_original'].memberId;
|
||||||
urlValue = `/practice-summary-report/${memberId}`;
|
urlValue = `/specific-member-incidents-report/${memberId}`;
|
||||||
cellValue = props.value;
|
cellValue = props.value;
|
||||||
break;
|
break;
|
||||||
case 'resourceName':
|
case 'resourceName':
|
||||||
|
|||||||
@@ -1,7 +1,8 @@
|
|||||||
import UploadDLockData from '../scenes/UploadDLockData';
|
import UploadDLockData from '../scenes/UploadDLockData';
|
||||||
import Home from '../scenes/Home';
|
import Home from '../scenes/Home';
|
||||||
import IncidentsReport from '../scenes/IncidentsReport';
|
import IncidentsReport from '../scenes/IncidentsReport';
|
||||||
import PracticeSummaryReport from '../scenes/PracticeSummaryReport';
|
import SpecificMemberIncidentsReport from '../scenes/SpecificMemberIncidentsReport';
|
||||||
|
import MemberPracticeSummaryReport from '../scenes/MemberPracticeSummaryReport';
|
||||||
|
|
||||||
export const mainMenuItems = [
|
export const mainMenuItems = [
|
||||||
{
|
{
|
||||||
@@ -12,17 +13,24 @@ export const mainMenuItems = [
|
|||||||
component: Home,
|
component: Home,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 'practiceSummaryReport',
|
id: 'memberPracticeSummaryReport',
|
||||||
showInMenu: true,
|
showInMenu: true,
|
||||||
title: 'Practice Summary Report',
|
title: 'Member Practice Summary Report',
|
||||||
url: '/practice-summary-report',
|
url: '/member-practice-summary-report',
|
||||||
component: PracticeSummaryReport,
|
component: MemberPracticeSummaryReport,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 'practiceSummaryReportWithMemberId',
|
id: 'specificMemberIncidentsReport',
|
||||||
|
showInMenu: true,
|
||||||
|
title: 'Member Incidents Report',
|
||||||
|
url: '/specific-member-incidents-report',
|
||||||
|
component: SpecificMemberIncidentsReport,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'specificMemberIncidentsReportWithMemberId',
|
||||||
showInMenu: false,
|
showInMenu: false,
|
||||||
url: '/practice-summary-report/:memberId',
|
url: '/specific-member-incidents-report/:memberId',
|
||||||
component: PracticeSummaryReport,
|
component: SpecificMemberIncidentsReport,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 'report',
|
id: 'report',
|
||||||
|
|||||||
33
client/src/scenes/MemberPracticeSummaryReport/index.js
Normal file
33
client/src/scenes/MemberPracticeSummaryReport/index.js
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
import React, { Component } from 'react';
|
||||||
|
import { connect } from 'react-redux';
|
||||||
|
import { Container, Button, Loader } from 'semantic-ui-react';
|
||||||
|
|
||||||
|
import MainMenu from '../../components/MainMenu';
|
||||||
|
|
||||||
|
import { fetchMemberPracticeSummaryReport } from '../../store/actions';
|
||||||
|
|
||||||
|
class MemberPracticeSummaryReport extends Component {
|
||||||
|
render () {
|
||||||
|
const { fetchMemberPracticeSummaryReport, pendingReport } = this.props;
|
||||||
|
return (
|
||||||
|
<Container>
|
||||||
|
<MainMenu/>
|
||||||
|
<h3>Member Practice Summary Report</h3>
|
||||||
|
<hr/>
|
||||||
|
<br/>
|
||||||
|
<Loader active={pendingReport} />
|
||||||
|
<Button disabled={pendingReport} onClick={fetchMemberPracticeSummaryReport}>Generate Report</Button>
|
||||||
|
</Container>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const mapStateToProps = (state) => ({
|
||||||
|
pendingReport: state.memberPracticeSummaryReport.pending,
|
||||||
|
});
|
||||||
|
|
||||||
|
const mapDispatchToProps = (dispatch) => ({
|
||||||
|
fetchMemberPracticeSummaryReport: () => fetchMemberPracticeSummaryReport(dispatch),
|
||||||
|
});
|
||||||
|
|
||||||
|
export default connect(mapStateToProps, mapDispatchToProps)(MemberPracticeSummaryReport);
|
||||||
@@ -11,7 +11,7 @@ import GenerateFeesInORDButton from '../../components/GenerateFeesInORDButton';
|
|||||||
|
|
||||||
import { fetchMemberIncidents } from '../../store/actions';
|
import { fetchMemberIncidents } from '../../store/actions';
|
||||||
|
|
||||||
class PracticeSummaryReport extends Component {
|
class SpecificMemberIncidentsReport extends Component {
|
||||||
constructor(props){
|
constructor(props){
|
||||||
super(props);
|
super(props);
|
||||||
|
|
||||||
@@ -50,7 +50,7 @@ class PracticeSummaryReport extends Component {
|
|||||||
return (
|
return (
|
||||||
<Container>
|
<Container>
|
||||||
<MainMenu/>
|
<MainMenu/>
|
||||||
<h3>Practice Summary Report</h3>
|
<h3>Member Incidents Report</h3>
|
||||||
<hr/>
|
<hr/>
|
||||||
<Grid stackable columns="equal">
|
<Grid stackable columns="equal">
|
||||||
<Grid.Row>
|
<Grid.Row>
|
||||||
@@ -101,4 +101,4 @@ const mapDispatchToProps = (dispatch) => ({
|
|||||||
fetchMemberIncidents: (memberId, dateRange) => fetchMemberIncidents(dispatch, memberId, dateRange),
|
fetchMemberIncidents: (memberId, dateRange) => fetchMemberIncidents(dispatch, memberId, dateRange),
|
||||||
});
|
});
|
||||||
|
|
||||||
export default connect(mapStateToProps, mapDispatchToProps)(PracticeSummaryReport);
|
export default connect(mapStateToProps, mapDispatchToProps)(SpecificMemberIncidentsReport);
|
||||||
@@ -20,6 +20,9 @@ import {
|
|||||||
CHECK_PROCESSING_PENDING,
|
CHECK_PROCESSING_PENDING,
|
||||||
CHECK_PROCESSING_SUCCESS,
|
CHECK_PROCESSING_SUCCESS,
|
||||||
CHECK_PROCESSING_FAILED,
|
CHECK_PROCESSING_FAILED,
|
||||||
|
FETCH_MEMBER_PRACTICE_SUMMARY_REPORT_PENDING,
|
||||||
|
FETCH_MEMBER_PRACTICE_SUMMARY_REPORT_SUCCESS,
|
||||||
|
FETCH_MEMBER_PRACTICE_SUMMARY_REPORT_FAILED,
|
||||||
} from '../constants';
|
} from '../constants';
|
||||||
|
|
||||||
import API from '../../utilities/api';
|
import API from '../../utilities/api';
|
||||||
@@ -109,3 +112,16 @@ export const checkProcessing = (dispatch) => {
|
|||||||
dispatch({type: CHECK_PROCESSING_FAILED, payload: error.response});
|
dispatch({type: CHECK_PROCESSING_FAILED, payload: error.response});
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const fetchMemberPracticeSummaryReport = (dispatch) => {
|
||||||
|
dispatch({type: FETCH_MEMBER_PRACTICE_SUMMARY_REPORT_PENDING});
|
||||||
|
API.get('integration/report/practiceSummary', {
|
||||||
|
responseType: 'blob',
|
||||||
|
})
|
||||||
|
.then(response => {
|
||||||
|
dispatch({type: FETCH_MEMBER_PRACTICE_SUMMARY_REPORT_SUCCESS, payload: response});
|
||||||
|
})
|
||||||
|
.catch(error => {
|
||||||
|
dispatch({type: FETCH_MEMBER_PRACTICE_SUMMARY_REPORT_FAILED, payload: error.response});
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|||||||
@@ -29,3 +29,7 @@ export const ADD_FEES_TO_ORD_FAILED = 'ADD_FEES_TO_ORD_FAILED';
|
|||||||
export const CHECK_PROCESSING_PENDING = 'CHECK_PROCESSING_PENDING';
|
export const CHECK_PROCESSING_PENDING = 'CHECK_PROCESSING_PENDING';
|
||||||
export const CHECK_PROCESSING_SUCCESS = 'CHECK_PROCESSING_SUCCESS';
|
export const CHECK_PROCESSING_SUCCESS = 'CHECK_PROCESSING_SUCCESS';
|
||||||
export const CHECK_PROCESSING_FAILED = 'CHECK_PROCESSING_FAILED';
|
export const CHECK_PROCESSING_FAILED = 'CHECK_PROCESSING_FAILED';
|
||||||
|
|
||||||
|
export const FETCH_MEMBER_PRACTICE_SUMMARY_REPORT_PENDING = 'FETCH_MEMBER_PRACTICE_SUMMARY_REPORT_PENDING';
|
||||||
|
export const FETCH_MEMBER_PRACTICE_SUMMARY_REPORT_SUCCESS = 'FETCH_MEMBER_PRACTICE_SUMMARY_REPORT_SUCCESS';
|
||||||
|
export const FETCH_MEMBER_PRACTICE_SUMMARY_REPORT_FAILED = 'FETCH_MEMBER_PRACTICE_SUMMARY_REPORT_FAILED';
|
||||||
|
|||||||
@@ -0,0 +1,45 @@
|
|||||||
|
import {
|
||||||
|
FETCH_MEMBER_PRACTICE_SUMMARY_REPORT_PENDING,
|
||||||
|
FETCH_MEMBER_PRACTICE_SUMMARY_REPORT_SUCCESS,
|
||||||
|
FETCH_MEMBER_PRACTICE_SUMMARY_REPORT_FAILED,
|
||||||
|
} from '../constants';
|
||||||
|
|
||||||
|
const initialState = {
|
||||||
|
pending: false,
|
||||||
|
result: null,
|
||||||
|
error: null,
|
||||||
|
};
|
||||||
|
|
||||||
|
export const memberPracticeSummaryReport = (state, action) => {
|
||||||
|
state = state || initialState;
|
||||||
|
action = action || {};
|
||||||
|
|
||||||
|
switch(action.type){
|
||||||
|
case FETCH_MEMBER_PRACTICE_SUMMARY_REPORT_PENDING:
|
||||||
|
return Object.assign({}, state, {
|
||||||
|
pending: true,
|
||||||
|
error: null,
|
||||||
|
});
|
||||||
|
case FETCH_MEMBER_PRACTICE_SUMMARY_REPORT_SUCCESS:
|
||||||
|
const url = window.URL.createObjectURL(new Blob([action.payload.data]));
|
||||||
|
const link = document.createElement('a');
|
||||||
|
link.href = url;
|
||||||
|
link.setAttribute('download', 'Member Practice Summary Report.xlsx');
|
||||||
|
document.body.appendChild(link);
|
||||||
|
link.click();
|
||||||
|
|
||||||
|
return Object.assign({}, state, {
|
||||||
|
pending: false,
|
||||||
|
result: null,
|
||||||
|
error: null,
|
||||||
|
});
|
||||||
|
case FETCH_MEMBER_PRACTICE_SUMMARY_REPORT_FAILED:
|
||||||
|
return Object.assign({}, state, {
|
||||||
|
pending: false,
|
||||||
|
result: {},
|
||||||
|
error: action.payload,
|
||||||
|
});
|
||||||
|
default:
|
||||||
|
return state;
|
||||||
|
}
|
||||||
|
};
|
||||||
@@ -8,6 +8,7 @@ import { membersList } from './membersListReducer';
|
|||||||
import { memberIncidents} from './memberIncidentsReducer';
|
import { memberIncidents} from './memberIncidentsReducer';
|
||||||
import { addFeesStatus } from './addFeesToOrdReducer';
|
import { addFeesStatus } from './addFeesToOrdReducer';
|
||||||
import { checkProcessing } from './checkProcessingReducer';
|
import { checkProcessing } from './checkProcessingReducer';
|
||||||
|
import { memberPracticeSummaryReport} from './fetchMemberPracticeSummaryReportReducer';
|
||||||
|
|
||||||
export const rootReducer = combineReducers({
|
export const rootReducer = combineReducers({
|
||||||
doorLockData,
|
doorLockData,
|
||||||
@@ -18,5 +19,6 @@ export const rootReducer = combineReducers({
|
|||||||
memberIncidents,
|
memberIncidents,
|
||||||
addFeesStatus,
|
addFeesStatus,
|
||||||
checkProcessing,
|
checkProcessing,
|
||||||
|
memberPracticeSummaryReport,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -68,6 +68,8 @@ const integrationServiceErrors = {
|
|||||||
FAILED_TO_SAVE_DOOR_LOCK_ENTRIES: 'Failed to save door lock entries',
|
FAILED_TO_SAVE_DOOR_LOCK_ENTRIES: 'Failed to save door lock entries',
|
||||||
FAILED_TO_SAVE_DATA_GENERIC: 'Failed to save data',
|
FAILED_TO_SAVE_DATA_GENERIC: 'Failed to save data',
|
||||||
INVALID_DATE_RANGE: 'Dates in date range are invalid',
|
INVALID_DATE_RANGE: 'Dates in date range are invalid',
|
||||||
|
FAILED_TO_GENERATE_MEMBER_PRACTICE_SUMMARY: 'Failed to generate Member Practice Summary',
|
||||||
|
ERRORS_IN_MEMBER_PRACTICE_SUMMARY_REPORT: 'Member Practice Summary Report is generated but there were some errors and report may be incomplete',
|
||||||
};
|
};
|
||||||
|
|
||||||
const incidentType = {
|
const incidentType = {
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
const { getMappingsFromDatabase, fetchOffices, fetchResources, saveNewMappingToDatabase } = require('../services/officeRnD/resources');
|
const { getMappingsFromDatabase, fetchOffices, fetchResources, saveNewMappingToDatabase } = require('../services/officeRnD/resources');
|
||||||
const { getAllIncidents } = require('../services/integration/reports');
|
const { getAllIncidents, getMemberPracticeSummaryReport } = require('../services/integration/reports');
|
||||||
const { getMembersFeesForDateRange } = require('../services/integration/invoiceIntegration');
|
const { getMembersFeesForDateRange } = require('../services/integration/invoiceIntegration');
|
||||||
const { deleteFeesFromORD, addFeesToORD } = require('../services/officeRnD/fees');
|
const { deleteFeesFromORD, addFeesToORD } = require('../services/officeRnD/fees');
|
||||||
const { checkBookingChanges } = require('../services/integration/checkBookingChange');
|
const { checkBookingChanges } = require('../services/integration/checkBookingChange');
|
||||||
@@ -126,6 +126,19 @@ const checkProcessingStatus = (req, res) => {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const getPracticeSummaryReport = (req, res) => {
|
||||||
|
getMemberPracticeSummaryReport()
|
||||||
|
.then((result) => {
|
||||||
|
const pathToDownloadFile = `${__dirname}/../${result.reportPath}`;
|
||||||
|
res.download(pathToDownloadFile);
|
||||||
|
})
|
||||||
|
.catch((error) => {
|
||||||
|
console.log('Error - Member Practice Summary Report');
|
||||||
|
console.log(error);
|
||||||
|
res.status(500).send(error);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
getKnownOfficeResourceMappings,
|
getKnownOfficeResourceMappings,
|
||||||
addNewMapping,
|
addNewMapping,
|
||||||
@@ -133,4 +146,5 @@ module.exports = {
|
|||||||
getMemberIncidents,
|
getMemberIncidents,
|
||||||
addFees,
|
addFees,
|
||||||
checkProcessingStatus,
|
checkProcessingStatus,
|
||||||
|
getPracticeSummaryReport,
|
||||||
};
|
};
|
||||||
|
|||||||
99
package-lock.json
generated
99
package-lock.json
generated
@@ -693,6 +693,11 @@
|
|||||||
"integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==",
|
"integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
|
"deepmerge": {
|
||||||
|
"version": "3.2.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-3.2.0.tgz",
|
||||||
|
"integrity": "sha512-6+LuZGU7QCNUnAJyX8cIrlzoEgggTM6B7mm+znKOX4t5ltluT9KLjN6g61ECMS0LTsLW7yDpNoxhix5FZcrIow=="
|
||||||
|
},
|
||||||
"define-property": {
|
"define-property": {
|
||||||
"version": "2.0.2",
|
"version": "2.0.2",
|
||||||
"resolved": "https://registry.npmjs.org/define-property/-/define-property-2.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/define-property/-/define-property-2.0.2.tgz",
|
||||||
@@ -863,6 +868,31 @@
|
|||||||
"es5-ext": "~0.10.14"
|
"es5-ext": "~0.10.14"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"excel4node": {
|
||||||
|
"version": "1.7.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/excel4node/-/excel4node-1.7.2.tgz",
|
||||||
|
"integrity": "sha512-3XSsPSSbUeGloTRQMTmgf4aA1WDGa9fWQitOCGN/URG8NvwPjgVdbyG7FWKukqTdl5XBz9jv0vVBz3BBIo52tg==",
|
||||||
|
"requires": {
|
||||||
|
"deepmerge": "3.2.0",
|
||||||
|
"image-size": "0.7.2",
|
||||||
|
"jszip": "3.2.1",
|
||||||
|
"lodash.get": "4.4.2",
|
||||||
|
"lodash.isequal": "4.5.0",
|
||||||
|
"lodash.isundefined": "3.0.1",
|
||||||
|
"lodash.reduce": "4.6.0",
|
||||||
|
"lodash.uniqueid": "4.0.1",
|
||||||
|
"mime": "2.4.0",
|
||||||
|
"uuid": "3.3.2",
|
||||||
|
"xmlbuilder": "11.0.1"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"mime": {
|
||||||
|
"version": "2.4.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/mime/-/mime-2.4.0.tgz",
|
||||||
|
"integrity": "sha512-ikBcWwyqXQSHKtciCcctu9YfPbFYZ4+gbHEmE0Q8jzcTYQg5dHCr3g2wwAZjPoJfQVXZq6KXAjpXOTf5/cjT7w=="
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"execa": {
|
"execa": {
|
||||||
"version": "0.7.0",
|
"version": "0.7.0",
|
||||||
"resolved": "https://registry.npmjs.org/execa/-/execa-0.7.0.tgz",
|
"resolved": "https://registry.npmjs.org/execa/-/execa-0.7.0.tgz",
|
||||||
@@ -1911,6 +1941,16 @@
|
|||||||
"integrity": "sha1-SMptcvbGo68Aqa1K5odr44ieKwk=",
|
"integrity": "sha1-SMptcvbGo68Aqa1K5odr44ieKwk=",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
|
"image-size": {
|
||||||
|
"version": "0.7.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/image-size/-/image-size-0.7.2.tgz",
|
||||||
|
"integrity": "sha512-CBmVIFHyDyiWi1U24eNHl8SH0Iir2IgmEv1RwdRVZxWsEbSCvV5b/eXaYP8epOFv2dbw5uNBOrn1Nc5P5KvsUA=="
|
||||||
|
},
|
||||||
|
"immediate": {
|
||||||
|
"version": "3.0.6",
|
||||||
|
"resolved": "https://registry.npmjs.org/immediate/-/immediate-3.0.6.tgz",
|
||||||
|
"integrity": "sha1-nbHb0Pr43m++D13V5Wu2BigN5ps="
|
||||||
|
},
|
||||||
"import-lazy": {
|
"import-lazy": {
|
||||||
"version": "2.1.0",
|
"version": "2.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/import-lazy/-/import-lazy-2.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/import-lazy/-/import-lazy-2.1.0.tgz",
|
||||||
@@ -2216,6 +2256,17 @@
|
|||||||
"graceful-fs": "^4.1.6"
|
"graceful-fs": "^4.1.6"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"jszip": {
|
||||||
|
"version": "3.2.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/jszip/-/jszip-3.2.1.tgz",
|
||||||
|
"integrity": "sha512-iCMBbo4eE5rb1VCpm5qXOAaUiRKRUKiItn8ah2YQQx9qymmSAY98eyQfioChEYcVQLh0zxJ3wS4A0mh90AVPvw==",
|
||||||
|
"requires": {
|
||||||
|
"lie": "~3.3.0",
|
||||||
|
"pako": "~1.0.2",
|
||||||
|
"readable-stream": "~2.3.6",
|
||||||
|
"set-immediate-shim": "~1.0.1"
|
||||||
|
}
|
||||||
|
},
|
||||||
"kind-of": {
|
"kind-of": {
|
||||||
"version": "6.0.2",
|
"version": "6.0.2",
|
||||||
"resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.2.tgz",
|
||||||
@@ -2239,6 +2290,14 @@
|
|||||||
"invert-kv": "^2.0.0"
|
"invert-kv": "^2.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"lie": {
|
||||||
|
"version": "3.3.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/lie/-/lie-3.3.0.tgz",
|
||||||
|
"integrity": "sha512-UaiMJzeWRlEujzAuw5LokY1L5ecNQYZKfmyZ9L7wDHb/p5etKaxXhohBcrw0EYby+G/NA52vRSN4N39dxHAIwQ==",
|
||||||
|
"requires": {
|
||||||
|
"immediate": "~3.0.5"
|
||||||
|
}
|
||||||
|
},
|
||||||
"locate-path": {
|
"locate-path": {
|
||||||
"version": "3.0.0",
|
"version": "3.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz",
|
||||||
@@ -2253,6 +2312,31 @@
|
|||||||
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.11.tgz",
|
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.11.tgz",
|
||||||
"integrity": "sha512-cQKh8igo5QUhZ7lg38DYWAxMvjSAKG0A8wGSVimP07SIUEK2UO+arSRKbRZWtelMtN5V0Hkwh5ryOto/SshYIg=="
|
"integrity": "sha512-cQKh8igo5QUhZ7lg38DYWAxMvjSAKG0A8wGSVimP07SIUEK2UO+arSRKbRZWtelMtN5V0Hkwh5ryOto/SshYIg=="
|
||||||
},
|
},
|
||||||
|
"lodash.get": {
|
||||||
|
"version": "4.4.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/lodash.get/-/lodash.get-4.4.2.tgz",
|
||||||
|
"integrity": "sha1-LRd/ZS+jHpObRDjVNBSZ36OCXpk="
|
||||||
|
},
|
||||||
|
"lodash.isequal": {
|
||||||
|
"version": "4.5.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/lodash.isequal/-/lodash.isequal-4.5.0.tgz",
|
||||||
|
"integrity": "sha1-QVxEePK8wwEgwizhDtMib30+GOA="
|
||||||
|
},
|
||||||
|
"lodash.isundefined": {
|
||||||
|
"version": "3.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/lodash.isundefined/-/lodash.isundefined-3.0.1.tgz",
|
||||||
|
"integrity": "sha1-I+89lTVWUgOmbO/VuDD4SJEa+0g="
|
||||||
|
},
|
||||||
|
"lodash.reduce": {
|
||||||
|
"version": "4.6.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/lodash.reduce/-/lodash.reduce-4.6.0.tgz",
|
||||||
|
"integrity": "sha1-8atrg5KZrUj3hKu/R2WW8DuRTTs="
|
||||||
|
},
|
||||||
|
"lodash.uniqueid": {
|
||||||
|
"version": "4.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/lodash.uniqueid/-/lodash.uniqueid-4.0.1.tgz",
|
||||||
|
"integrity": "sha1-MmjyanyI5PSxdY1nknGBTjH6WyY="
|
||||||
|
},
|
||||||
"lowercase-keys": {
|
"lowercase-keys": {
|
||||||
"version": "1.0.1",
|
"version": "1.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-1.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-1.0.1.tgz",
|
||||||
@@ -2751,6 +2835,11 @@
|
|||||||
"resolved": "https://registry.npmjs.org/packet-reader/-/packet-reader-1.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/packet-reader/-/packet-reader-1.0.0.tgz",
|
||||||
"integrity": "sha512-HAKu/fG3HpHFO0AA8WE8q2g+gBJaZ9MG7fcKk+IJPLTGAD6Psw4443l+9DGRbOIh3/aXr7Phy0TjilYivJo5XQ=="
|
"integrity": "sha512-HAKu/fG3HpHFO0AA8WE8q2g+gBJaZ9MG7fcKk+IJPLTGAD6Psw4443l+9DGRbOIh3/aXr7Phy0TjilYivJo5XQ=="
|
||||||
},
|
},
|
||||||
|
"pako": {
|
||||||
|
"version": "1.0.10",
|
||||||
|
"resolved": "https://registry.npmjs.org/pako/-/pako-1.0.10.tgz",
|
||||||
|
"integrity": "sha512-0DTvPVU3ed8+HNXOu5Bs+o//Mbdj9VNQMUOe9oKCwh8l0GNwpTDMKCWbRjgtD291AWnkAgkqA/LOnQS8AmS1tw=="
|
||||||
|
},
|
||||||
"parseurl": {
|
"parseurl": {
|
||||||
"version": "1.3.3",
|
"version": "1.3.3",
|
||||||
"resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz",
|
"resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz",
|
||||||
@@ -3214,6 +3303,11 @@
|
|||||||
"resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz",
|
||||||
"integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc="
|
"integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc="
|
||||||
},
|
},
|
||||||
|
"set-immediate-shim": {
|
||||||
|
"version": "1.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/set-immediate-shim/-/set-immediate-shim-1.0.1.tgz",
|
||||||
|
"integrity": "sha1-SysbJ+uAip+NzEgaWOXlb1mfP2E="
|
||||||
|
},
|
||||||
"set-value": {
|
"set-value": {
|
||||||
"version": "2.0.0",
|
"version": "2.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/set-value/-/set-value-2.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/set-value/-/set-value-2.0.0.tgz",
|
||||||
@@ -3920,6 +4014,11 @@
|
|||||||
"integrity": "sha1-SWsswQnsqNus/i3HK2A8F8WHCtQ=",
|
"integrity": "sha1-SWsswQnsqNus/i3HK2A8F8WHCtQ=",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
|
"xmlbuilder": {
|
||||||
|
"version": "11.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-11.0.1.tgz",
|
||||||
|
"integrity": "sha512-fDlsI/kFEx7gLvbecc0/ohLG50fugQp8ryHzMTuW9vSa1GJ0XYWKnhsUx7oie3G98+r56aTQIUB4kht42R3JvA=="
|
||||||
|
},
|
||||||
"xtend": {
|
"xtend": {
|
||||||
"version": "4.0.1",
|
"version": "4.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.1.tgz",
|
||||||
|
|||||||
@@ -25,6 +25,7 @@
|
|||||||
"axios": "^0.19.0",
|
"axios": "^0.19.0",
|
||||||
"csv-parser": "^2.3.0",
|
"csv-parser": "^2.3.0",
|
||||||
"dotenv": "^8.0.0",
|
"dotenv": "^8.0.0",
|
||||||
|
"excel4node": "^1.7.2",
|
||||||
"express": "^4.17.0",
|
"express": "^4.17.0",
|
||||||
"express-basic-auth": "^1.2.0",
|
"express-basic-auth": "^1.2.0",
|
||||||
"formidable": "^1.2.1",
|
"formidable": "^1.2.1",
|
||||||
|
|||||||
1
report_sheets/.gitignore
vendored
Normal file
1
report_sheets/.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
|||||||
|
*.xlsx
|
||||||
@@ -10,6 +10,7 @@ const {
|
|||||||
getMemberIncidents,
|
getMemberIncidents,
|
||||||
addFees,
|
addFees,
|
||||||
checkProcessingStatus,
|
checkProcessingStatus,
|
||||||
|
getPracticeSummaryReport,
|
||||||
} = require('../controllers/integration');
|
} = require('../controllers/integration');
|
||||||
|
|
||||||
const { calculateDoorLockCharges } = require('../services/integration/doorLockCharges');
|
const { calculateDoorLockCharges } = require('../services/integration/doorLockCharges');
|
||||||
@@ -32,6 +33,10 @@ router.post('/integration/addFees', addFees);
|
|||||||
|
|
||||||
router.get('/integration/processing', checkProcessingStatus);
|
router.get('/integration/processing', checkProcessingStatus);
|
||||||
|
|
||||||
|
router.get('/integration/report/practiceSummary', getPracticeSummaryReport);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// temporary route, manually trigger door lock charge calculations
|
// temporary route, manually trigger door lock charge calculations
|
||||||
router.get('/calculate', (req, res) => { calculateDoorLockCharges(); res.send();});
|
router.get('/calculate', (req, res) => { calculateDoorLockCharges(); res.send();});
|
||||||
|
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
const moment = require('moment-timezone');
|
const moment = require('moment-timezone');
|
||||||
const db = require('../../models/index');
|
const db = require('../../models/index');
|
||||||
|
const Op = require('sequelize').Op;
|
||||||
|
|
||||||
const { UI_TIMEZONE, BOOKING_CHANGE_PERCENTAGE_CHARGE, ALLOWED_BOOKING_CANCELLATION_TIME, incidentType } = require('../../constants/constants');
|
const { UI_TIMEZONE, BOOKING_CHANGE_PERCENTAGE_CHARGE, ALLOWED_BOOKING_CANCELLATION_TIME, incidentType } = require('../../constants/constants');
|
||||||
|
|
||||||
@@ -128,6 +129,20 @@ const chargeBookingChanges = (changes) => {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const getChargedCanceledReservations = (reservationIds) => {
|
||||||
|
const filters = {
|
||||||
|
reservationId: {
|
||||||
|
[Op.in]: reservationIds,
|
||||||
|
},
|
||||||
|
incidentType: incidentType.BOOKING_CANCELED_LATE,
|
||||||
|
};
|
||||||
|
|
||||||
|
const attributes = ['memberId', 'oldBookingStart', 'oldBookingEnd'];
|
||||||
|
|
||||||
|
return db.bookingChangeIncident.findAll({attributes, where: filters});
|
||||||
|
};
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
chargeBookingChanges,
|
chargeBookingChanges,
|
||||||
|
getChargedCanceledReservations,
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -48,6 +48,41 @@ const getActiveBookingsForMembersInDateRange = (dateRange, memberIds) => {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const getAllBookingsForYear = (year) => {
|
||||||
|
const startDate = moment.tz(year, 'YYYY', UI_TIMEZONE).startOf('year');
|
||||||
|
const endDate = moment.tz(year, 'YYYY', UI_TIMEZONE).endOf('year');
|
||||||
|
|
||||||
|
const attributes = [
|
||||||
|
'id',
|
||||||
|
'reservationId',
|
||||||
|
'memberId',
|
||||||
|
'officeId',
|
||||||
|
'resourceId',
|
||||||
|
'start',
|
||||||
|
'end',
|
||||||
|
'timezone',
|
||||||
|
'canceled',
|
||||||
|
'hourlyRate'
|
||||||
|
];
|
||||||
|
|
||||||
|
const filters = {};
|
||||||
|
|
||||||
|
if (startDate && endDate) {
|
||||||
|
filters.start = {
|
||||||
|
[Op.gte]: startDate.toISOString()
|
||||||
|
};
|
||||||
|
filters.end = {
|
||||||
|
[Op.lte]: endDate.toISOString(),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return db.bookingReservation.findAll({
|
||||||
|
attributes,
|
||||||
|
where: filters,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
getActiveBookingsForMembersInDateRange,
|
getActiveBookingsForMembersInDateRange,
|
||||||
|
getAllBookingsForYear,
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -5,10 +5,15 @@ const moment = require('moment-timezone');
|
|||||||
const db = require('../../models/index');
|
const db = require('../../models/index');
|
||||||
const Op = require('sequelize').Op;
|
const Op = require('sequelize').Op;
|
||||||
|
|
||||||
|
const workbookCreator = require('excel4node');
|
||||||
|
|
||||||
|
const { checkBookingChanges } = require('./checkBookingChange');
|
||||||
const { incidentType, UI_TIMEZONE, DEFAULT_DATE_FORMAT, integrationServiceErrors } = require('../../constants/constants');
|
const { incidentType, UI_TIMEZONE, DEFAULT_DATE_FORMAT, integrationServiceErrors } = require('../../constants/constants');
|
||||||
|
|
||||||
|
const { getAllBookingsForYear } = require('./bookings');
|
||||||
const { fetchAllMembers } = require('../officeRnD/members');
|
const { fetchAllMembers } = require('../officeRnD/members');
|
||||||
const { fetchOffices, fetchResources } = require('../officeRnD/resources');
|
const { fetchOffices, fetchResources } = require('../officeRnD/resources');
|
||||||
|
const { getChargedCanceledReservations } = require('../integration/bookingChangeCharges');
|
||||||
|
|
||||||
const getUnlockedIncidents = (startDate, endDate, memberIds) => {
|
const getUnlockedIncidents = (startDate, endDate, memberIds) => {
|
||||||
const attributes = ['id', 'reservationId', 'memberId', 'resourceId', 'bookingStart', 'bookingEnd', 'unlockTimestamp', 'incidentLevel', 'incidentLevelPrice'];
|
const attributes = ['id', 'reservationId', 'memberId', 'resourceId', 'bookingStart', 'bookingEnd', 'unlockTimestamp', 'incidentLevel', 'incidentLevelPrice'];
|
||||||
@@ -313,6 +318,278 @@ const getAllIncidents = (dateRange, memberIds) => {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const getMemberPracticeSummaryReport = (res) => {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
const year = moment.tz(UI_TIMEZONE).year();
|
||||||
|
|
||||||
|
const asyncJobs = [checkBookingChanges(), getAllBookingsForYear(year), fetchAllMembers()];
|
||||||
|
|
||||||
|
Promise.all(asyncJobs)
|
||||||
|
.then((results) => {
|
||||||
|
const allBookings = results[1];
|
||||||
|
const allMembers = results[2];
|
||||||
|
|
||||||
|
const membersMap = {};
|
||||||
|
|
||||||
|
allMembers.forEach((member) => {
|
||||||
|
membersMap[member.memberId] = member;
|
||||||
|
});
|
||||||
|
|
||||||
|
const reportMap = {};
|
||||||
|
|
||||||
|
const oneMonthObject = {
|
||||||
|
totalBookedHours: 0,
|
||||||
|
totalChargedHours: 0,
|
||||||
|
cancellationPercentage: 0,
|
||||||
|
growthRate: 0,
|
||||||
|
};
|
||||||
|
|
||||||
|
const oneMemberObject = [];
|
||||||
|
for (let i = 0; i < 12; i++) {
|
||||||
|
oneMemberObject.push(Object.assign({}, oneMonthObject));
|
||||||
|
}
|
||||||
|
|
||||||
|
const reservationIdsForAdditionalData = [];
|
||||||
|
|
||||||
|
allBookings.forEach((booking) => {
|
||||||
|
const {reservationId, memberId, start, end, timezone, canceled} = booking.get();
|
||||||
|
const startMoment = moment.tz(start, timezone);
|
||||||
|
const endMoment = moment.tz(end, timezone);
|
||||||
|
|
||||||
|
if (startMoment.isValid() && endMoment.isValid()) {
|
||||||
|
const bookingMonth = startMoment.month();
|
||||||
|
const bookingLength = endMoment.diff(startMoment, 'hours', true);
|
||||||
|
|
||||||
|
if (!reportMap[memberId]) {
|
||||||
|
reportMap[memberId] = JSON.parse(JSON.stringify(oneMemberObject));
|
||||||
|
}
|
||||||
|
|
||||||
|
reportMap[memberId][bookingMonth].totalBookedHours += bookingLength;
|
||||||
|
|
||||||
|
if (canceled) {
|
||||||
|
reservationIdsForAdditionalData.push(reservationId);
|
||||||
|
} else {
|
||||||
|
reportMap[memberId][bookingMonth].totalChargedHours += bookingLength;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
getChargedCanceledReservations(reservationIdsForAdditionalData)
|
||||||
|
.then((incidents) => {
|
||||||
|
incidents.forEach((incident) => {
|
||||||
|
const {memberId, oldBookingStart, oldBookingEnd} = incident.get();
|
||||||
|
|
||||||
|
const startMoment = moment.tz(oldBookingStart, UI_TIMEZONE);
|
||||||
|
const endMoment = moment.tz(oldBookingEnd, UI_TIMEZONE);
|
||||||
|
|
||||||
|
if (startMoment.isValid() && endMoment.isValid()) {
|
||||||
|
const bookingMonth = startMoment.month();
|
||||||
|
const bookingLength = endMoment.diff(startMoment, 'hours', true);
|
||||||
|
|
||||||
|
reportMap[memberId][bookingMonth].totalChargedHours += bookingLength;
|
||||||
|
}
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
// Generate report sheet
|
||||||
|
|
||||||
|
const reportWorkbook = new workbookCreator.Workbook({});
|
||||||
|
const reportWorksheet = reportWorkbook.addWorksheet('Sheet 1');
|
||||||
|
|
||||||
|
const titleStyle = reportWorkbook.createStyle({
|
||||||
|
font: {
|
||||||
|
size: 18
|
||||||
|
}
|
||||||
|
});
|
||||||
|
const centeredStyle = reportWorkbook.createStyle({
|
||||||
|
alignment: {
|
||||||
|
horizontal: 'center',
|
||||||
|
}
|
||||||
|
});
|
||||||
|
const headerStyle = reportWorkbook.createStyle({
|
||||||
|
font: {
|
||||||
|
bold: true,
|
||||||
|
},
|
||||||
|
fill: {
|
||||||
|
type: 'pattern',
|
||||||
|
patternType: 'solid',
|
||||||
|
fgColor: '48cdd4',
|
||||||
|
}
|
||||||
|
});
|
||||||
|
const solidBlackBorder = {
|
||||||
|
left: {
|
||||||
|
style: 'thin',
|
||||||
|
color: '000000',
|
||||||
|
},
|
||||||
|
right: {
|
||||||
|
style: 'thin',
|
||||||
|
color: '000000',
|
||||||
|
},
|
||||||
|
top: {
|
||||||
|
style: 'thin',
|
||||||
|
color: '000000',
|
||||||
|
},
|
||||||
|
bottom: {
|
||||||
|
style: 'thin',
|
||||||
|
color: '000000',
|
||||||
|
}
|
||||||
|
};
|
||||||
|
const yellowCellStyle = reportWorkbook.createStyle({
|
||||||
|
fill: {
|
||||||
|
type: 'pattern',
|
||||||
|
patternType: 'solid',
|
||||||
|
fgColor: 'eaed37',
|
||||||
|
},
|
||||||
|
border: solidBlackBorder,
|
||||||
|
});
|
||||||
|
const redCellStyle = reportWorkbook.createStyle({
|
||||||
|
fill: {
|
||||||
|
type: 'pattern',
|
||||||
|
patternType: 'solid',
|
||||||
|
fgColor: 'e81717',
|
||||||
|
},
|
||||||
|
border: solidBlackBorder,
|
||||||
|
});
|
||||||
|
const greenCellStyle = reportWorkbook.createStyle({
|
||||||
|
fill: {
|
||||||
|
type: 'pattern',
|
||||||
|
patternType: 'solid',
|
||||||
|
fgColor: '329932',
|
||||||
|
},
|
||||||
|
border: solidBlackBorder,
|
||||||
|
});
|
||||||
|
const decimalPointStyle = reportWorkbook.createStyle({
|
||||||
|
numberFormat: '#,##0.00; -#,##0.00; -'
|
||||||
|
});
|
||||||
|
|
||||||
|
reportWorksheet.cell(2, 6, 2, 8, true).string('Member Practice Summary Report').style(titleStyle);
|
||||||
|
reportWorksheet.row(2).setHeight(20); // 20 is good height for font 18
|
||||||
|
|
||||||
|
reportWorksheet.cell(4, 6).string(`Year : ${year}`);
|
||||||
|
|
||||||
|
reportWorksheet.cell(7, 1).string('Member name');
|
||||||
|
reportWorksheet.column(1).setWidth(35);
|
||||||
|
|
||||||
|
const monthNames = ['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December'];
|
||||||
|
for (let i = 0; i < 12; i++) {
|
||||||
|
reportWorksheet.cell(6, 3 + i * 5, 6, 6 + i * 5, true).string(monthNames[i]).style(centeredStyle);
|
||||||
|
|
||||||
|
reportWorksheet.cell(7, 3 + i * 5).string('Total Booked [h]');
|
||||||
|
reportWorksheet.cell(7, 4 + i * 5).string('Total Charged [h]');
|
||||||
|
reportWorksheet.cell(7, 5 + i * 5).string('Cancellation [%]');
|
||||||
|
reportWorksheet.cell(7, 6 + i * 5).string('Growth [%]');
|
||||||
|
|
||||||
|
reportWorksheet.column(3 + i * 5).setWidth(20);
|
||||||
|
reportWorksheet.column(4 + i * 5).setWidth(20);
|
||||||
|
reportWorksheet.column(5 + i * 5).setWidth(20);
|
||||||
|
reportWorksheet.column(6 + i * 5).setWidth(20);
|
||||||
|
}
|
||||||
|
|
||||||
|
reportWorksheet.cell(7, 1, 7, 61).style(headerStyle);
|
||||||
|
|
||||||
|
const memberIdsListFromReportMap = Object.keys(reportMap);
|
||||||
|
|
||||||
|
const activeMemberIdsList = [];
|
||||||
|
const inactiveMemberIdsList = [];
|
||||||
|
|
||||||
|
memberIdsListFromReportMap.forEach((memberId) => {
|
||||||
|
if (membersMap[memberId].active){
|
||||||
|
activeMemberIdsList.push(memberId);
|
||||||
|
}else{
|
||||||
|
inactiveMemberIdsList.push(memberId);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const sortMemberIdsListByName = (memberId1, memberId2) => {
|
||||||
|
if (membersMap[memberId1].name > membersMap[memberId2].name){
|
||||||
|
return 1;
|
||||||
|
}else{
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
activeMemberIdsList.sort(sortMemberIdsListByName);
|
||||||
|
inactiveMemberIdsList.sort(sortMemberIdsListByName);
|
||||||
|
|
||||||
|
const populateReportForMember = (startRow, memberId, index) => {
|
||||||
|
|
||||||
|
const memberName = membersMap[memberId] ? membersMap[memberId].name : 'Unknown member';
|
||||||
|
reportWorksheet.cell(startRow + index, 1).string(memberName);
|
||||||
|
|
||||||
|
const memberReport = reportMap[memberId];
|
||||||
|
for (let i = 0; i< 12; i++){
|
||||||
|
const totalBookedHours = memberReport[i].totalBookedHours;
|
||||||
|
const totalChargedHoursCurrentMonth = memberReport[i].totalChargedHours;
|
||||||
|
let totalChargedHoursPreviousMonth = totalChargedHoursCurrentMonth;
|
||||||
|
if (i > 0){
|
||||||
|
totalChargedHoursPreviousMonth = memberReport[i-1].totalChargedHours;
|
||||||
|
}
|
||||||
|
|
||||||
|
let cancellationPercentage = ((totalBookedHours - totalChargedHoursCurrentMonth) / totalBookedHours)*100;
|
||||||
|
let growthRate = ((totalChargedHoursCurrentMonth - totalChargedHoursPreviousMonth) / totalChargedHoursPreviousMonth)*100;
|
||||||
|
if (isNaN(cancellationPercentage) || !cancellationPercentage){
|
||||||
|
cancellationPercentage = 0;
|
||||||
|
}
|
||||||
|
if (isNaN(growthRate) || growthRate > 100){
|
||||||
|
growthRate = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
const totalBookedHoursCell = reportWorksheet.cell(startRow + index, 3 + i*5);
|
||||||
|
const totalChargedHoursCell = reportWorksheet.cell(startRow + index, 4 + i*5);
|
||||||
|
const cancellationRateCell = reportWorksheet.cell(startRow + index, 5 + i*5);
|
||||||
|
const growthRateCell = reportWorksheet.cell(startRow + index, 6 + i*5);
|
||||||
|
|
||||||
|
totalBookedHoursCell.number(totalBookedHours).style(decimalPointStyle);
|
||||||
|
totalChargedHoursCell.number(totalChargedHoursCurrentMonth).style(decimalPointStyle);
|
||||||
|
cancellationRateCell.number(cancellationPercentage).style(decimalPointStyle);
|
||||||
|
growthRateCell.number(growthRate).style(decimalPointStyle);
|
||||||
|
|
||||||
|
if (cancellationPercentage > 30){
|
||||||
|
cancellationRateCell.style(yellowCellStyle);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (growthRate > 30){
|
||||||
|
growthRateCell.style(greenCellStyle);
|
||||||
|
}
|
||||||
|
if (growthRate < -30){
|
||||||
|
growthRateCell.style(redCellStyle);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const startRowForActiveMembers = 8;
|
||||||
|
activeMemberIdsList.forEach((memberId, index) => populateReportForMember(startRowForActiveMembers, memberId, index));
|
||||||
|
|
||||||
|
const inactiveMembersHeaderRow = startRowForActiveMembers + activeMemberIdsList.length + 1;
|
||||||
|
const startRowForInactiveMembers = inactiveMembersHeaderRow + 2;
|
||||||
|
reportWorksheet.cell(inactiveMembersHeaderRow, 1).string('Deactivated members').style(headerStyle);
|
||||||
|
|
||||||
|
inactiveMemberIdsList.forEach((memberId, index) => populateReportForMember(startRowForInactiveMembers, memberId, index));
|
||||||
|
|
||||||
|
reportWorksheet.cell(1, 1).string('Generated at : ');
|
||||||
|
reportWorksheet.cell(2, 1).string(moment.tz(UI_TIMEZONE).format('MMM DD, YYYY HH:mm a'));
|
||||||
|
|
||||||
|
const reportName = `${moment.tz(UI_TIMEZONE).format('DDMMYYYYHHmmss')}.xlsx`;
|
||||||
|
const reportPath = `report_sheets/${reportName}`;
|
||||||
|
reportWorkbook.write(reportPath, (error) => {
|
||||||
|
if (error){
|
||||||
|
reject(error);
|
||||||
|
}
|
||||||
|
resolve({reportPath});
|
||||||
|
});
|
||||||
|
})
|
||||||
|
.catch((error) => {
|
||||||
|
console.log('Error : ', error);
|
||||||
|
resolve({
|
||||||
|
report: reportMap,
|
||||||
|
error: integrationServiceErrors.ERRORS_IN_MEMBER_PRACTICE_SUMMARY_REPORT
|
||||||
|
});
|
||||||
|
});
|
||||||
|
})
|
||||||
|
.catch((error) => reject(error));
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
getAllIncidents,
|
getAllIncidents,
|
||||||
|
getMemberPracticeSummaryReport,
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ const fetchAllMembers = () => {
|
|||||||
name: member.name,
|
name: member.name,
|
||||||
memberId: member['_id'],
|
memberId: member['_id'],
|
||||||
teamId: member.team,
|
teamId: member.team,
|
||||||
|
active: member.status === 'active',
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
cleanedResult.sort((member1, member2) => (member1.name > member2.name) ? 1 : -1 );
|
cleanedResult.sort((member1, member2) => (member1.name > member2.name) ? 1 : -1 );
|
||||||
|
|||||||
Reference in New Issue
Block a user