Show incident report (without filtering for now)
This commit is contained in:
@@ -10,6 +10,7 @@
|
|||||||
"react-redux": "^7.0.3",
|
"react-redux": "^7.0.3",
|
||||||
"react-router-dom": "^5.0.0",
|
"react-router-dom": "^5.0.0",
|
||||||
"react-scripts": "3.0.1",
|
"react-scripts": "3.0.1",
|
||||||
|
"react-table": "^6.10.0",
|
||||||
"redux": "^4.0.1",
|
"redux": "^4.0.1",
|
||||||
"redux-thunk": "^2.3.0",
|
"redux-thunk": "^2.3.0",
|
||||||
"semantic-ui-css": "^2.4.1",
|
"semantic-ui-css": "^2.4.1",
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import React from 'react';
|
|||||||
import { NavLink } from 'react-router-dom';
|
import { NavLink } from 'react-router-dom';
|
||||||
import { Menu } from 'semantic-ui-react';
|
import { Menu } from 'semantic-ui-react';
|
||||||
|
|
||||||
import { mainMenuItems } from "../../constants/menuItems";
|
import { mainMenuItems } from '../../constants/menuItems';
|
||||||
|
|
||||||
const MainMenu = () =>
|
const MainMenu = () =>
|
||||||
(<Menu>
|
(<Menu>
|
||||||
|
|||||||
17
client/src/constants/enums.js
Normal file
17
client/src/constants/enums.js
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
|
||||||
|
export const UNLOCKED_INCIDENT = 2;
|
||||||
|
export const UNSCHEDULED_INCIDENT = 3;
|
||||||
|
|
||||||
|
export const incidentDescriptions = {};
|
||||||
|
incidentDescriptions[UNLOCKED_INCIDENT] = 'User left door unlocked';
|
||||||
|
incidentDescriptions[UNSCHEDULED_INCIDENT] = 'Unscheduled use';
|
||||||
|
|
||||||
|
export const incidentLevelDescriptions = {
|
||||||
|
UNLOCKED_0: 'First month',
|
||||||
|
UNLOCKED_1: 'Second month',
|
||||||
|
UNLOCKED_2: 'Third month',
|
||||||
|
UNLOCKED_3: 'Fourth month',
|
||||||
|
UNLOCKED_4: 'Fifth month',
|
||||||
|
UNLOCKED_5: 'Sixth month',
|
||||||
|
|
||||||
|
};
|
||||||
@@ -1,5 +1,6 @@
|
|||||||
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';
|
||||||
|
|
||||||
export const mainMenuItems = [
|
export const mainMenuItems = [
|
||||||
{
|
{
|
||||||
@@ -8,6 +9,12 @@ export const mainMenuItems = [
|
|||||||
url: '/',
|
url: '/',
|
||||||
component: Home,
|
component: Home,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
id: 'report',
|
||||||
|
title: 'Incidents Report',
|
||||||
|
url: '/incidents-report',
|
||||||
|
component: IncidentsReport,
|
||||||
|
},
|
||||||
{
|
{
|
||||||
id: 'uploadDLockData',
|
id: 'uploadDLockData',
|
||||||
title: 'DLock',
|
title: 'DLock',
|
||||||
@@ -15,3 +22,14 @@ export const mainMenuItems = [
|
|||||||
component: UploadDLockData,
|
component: UploadDLockData,
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
|
export const incidentsReportHeaderTitles = {
|
||||||
|
officeName: 'Office',
|
||||||
|
resourceName: 'Room',
|
||||||
|
bookingStart: 'Reservation Start',
|
||||||
|
bookingEnd: 'Reservation End',
|
||||||
|
memberName: 'Member Name',
|
||||||
|
incidentType: 'Incident Type',
|
||||||
|
feeDescription: 'Fee description',
|
||||||
|
totalChargeFee: 'Total Fee',
|
||||||
|
};
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import React, { Component } from 'react';
|
import React, { Component } from 'react';
|
||||||
|
|
||||||
import { Container, Form } from "semantic-ui-react";
|
import { Container, Form } from 'semantic-ui-react';
|
||||||
|
|
||||||
import MainMenu from '../../components/MainMenu';
|
import MainMenu from '../../components/MainMenu';
|
||||||
|
|
||||||
|
|||||||
108
client/src/scenes/IncidentsReport/index.js
Normal file
108
client/src/scenes/IncidentsReport/index.js
Normal file
@@ -0,0 +1,108 @@
|
|||||||
|
import React, { Component } from 'react';
|
||||||
|
import { connect } from 'react-redux';
|
||||||
|
import { Container, Loader } from 'semantic-ui-react';
|
||||||
|
import ReactTable from 'react-table';
|
||||||
|
import 'react-table/react-table.css';
|
||||||
|
|
||||||
|
import MainMenu from '../../components/MainMenu';
|
||||||
|
import { fetchIncidents } from '../../store/actions';
|
||||||
|
import { incidentsReportHeaderTitles } from '../../constants/menuItems';
|
||||||
|
import { incidentDescriptions, incidentLevelDescriptions, UNSCHEDULED_INCIDENT, UNLOCKED_INCIDENT } from '../../constants/enums';
|
||||||
|
|
||||||
|
class IncidentsReport extends Component {
|
||||||
|
|
||||||
|
componentDidMount() {
|
||||||
|
const { fetchIncidents } = this.props;
|
||||||
|
fetchIncidents();
|
||||||
|
}
|
||||||
|
|
||||||
|
render () {
|
||||||
|
const { pendingIncidents, incidents } = this.props;
|
||||||
|
|
||||||
|
const columns = [];
|
||||||
|
if (incidents && incidents.length > 0){
|
||||||
|
const incidentHeaders = Object.keys(incidentsReportHeaderTitles);
|
||||||
|
|
||||||
|
incidentHeaders.forEach((header) => {
|
||||||
|
const columnTitle = incidentsReportHeaderTitles[header];
|
||||||
|
|
||||||
|
if (columnTitle){
|
||||||
|
const columnAlignments = {
|
||||||
|
left: 'left',
|
||||||
|
right: 'right',
|
||||||
|
};
|
||||||
|
let columnContentsAlignment = columnAlignments.left;
|
||||||
|
|
||||||
|
columns.push({
|
||||||
|
Header: incidentsReportHeaderTitles[header],
|
||||||
|
accessor: header,
|
||||||
|
Cell: props => {
|
||||||
|
let cellValue;
|
||||||
|
|
||||||
|
switch (props.column.id) {
|
||||||
|
case 'incidentType':
|
||||||
|
cellValue = incidentDescriptions[props.value];
|
||||||
|
break;
|
||||||
|
case 'incidentLevel':
|
||||||
|
cellValue = incidentLevelDescriptions[props.value];
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'feeDescription':
|
||||||
|
const { incidentType, incidentLevel, timeIntervalsToCharge } = props.row['_original'];
|
||||||
|
switch (incidentType) {
|
||||||
|
case UNLOCKED_INCIDENT:
|
||||||
|
cellValue = `${incidentLevelDescriptions[incidentLevel]}`;
|
||||||
|
break;
|
||||||
|
case UNSCHEDULED_INCIDENT:
|
||||||
|
cellValue = `${timeIntervalsToCharge} x 5 min`;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'totalChargeFee':
|
||||||
|
const totalFee = props.value ? props.value : props.row['_original'].incidentPrice;
|
||||||
|
const totalFeeFormatted = parseFloat(totalFee).toFixed(2);
|
||||||
|
cellValue = `$ ${totalFeeFormatted}`;
|
||||||
|
columnContentsAlignment = columnAlignments.right;
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
cellValue = props.value;
|
||||||
|
}
|
||||||
|
|
||||||
|
return <div style={{ textAlign: columnContentsAlignment }}>{cellValue}</div>
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Container>
|
||||||
|
<MainMenu/>
|
||||||
|
<h3>Incidents Report</h3>
|
||||||
|
<hr/>
|
||||||
|
<Loader active={pendingIncidents} />
|
||||||
|
{
|
||||||
|
!pendingIncidents && incidents &&
|
||||||
|
<ReactTable
|
||||||
|
data={incidents}
|
||||||
|
multiSort={false}
|
||||||
|
columns={columns}
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
</Container>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const mapStateToProps = (state) => ({
|
||||||
|
pendingIncidents: state.incidentsReport.pending,
|
||||||
|
incidents: state.incidentsReport.result,
|
||||||
|
});
|
||||||
|
|
||||||
|
const mapDispatchToProps = (dispatch) => ({
|
||||||
|
fetchIncidents: () => fetchIncidents(dispatch),
|
||||||
|
});
|
||||||
|
|
||||||
|
export default connect(mapStateToProps, mapDispatchToProps)(IncidentsReport);
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import MainMenu from "../../components/MainMenu";
|
import MainMenu from '../../components/MainMenu';
|
||||||
|
|
||||||
export default function NotFound () {
|
export default function NotFound () {
|
||||||
return (
|
return (
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
import React, { Component } from 'react';
|
import React, { Component } from 'react';
|
||||||
import { connect } from 'react-redux';
|
import { connect } from 'react-redux';
|
||||||
import { Form } from "semantic-ui-react";
|
import { Form } from 'semantic-ui-react';
|
||||||
|
|
||||||
import UnknownMapping from './UnknownMapping';
|
import UnknownMapping from './UnknownMapping';
|
||||||
|
|
||||||
import { uploadDoorLockData, fetchMappings } from "../../../store/actions";
|
import { uploadDoorLockData, fetchMappings } from '../../../store/actions';
|
||||||
|
|
||||||
class FileUpload extends Component {
|
class FileUpload extends Component {
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
import React, { Component } from 'react';
|
import React, { Component } from 'react';
|
||||||
import { connect } from 'react-redux';
|
import { connect } from 'react-redux';
|
||||||
import {Button, Dropdown, Message} from "semantic-ui-react";
|
import {Button, Dropdown, Message} from 'semantic-ui-react';
|
||||||
import Fuse from 'fuse.js';
|
import Fuse from 'fuse.js';
|
||||||
import { addNewMapping } from "../../../store/actions";
|
import { addNewMapping } from '../../../store/actions';
|
||||||
|
|
||||||
class UnknownMapping extends Component {
|
class UnknownMapping extends Component {
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { Container, Form } from "semantic-ui-react";
|
import { Container, Form } from 'semantic-ui-react';
|
||||||
|
|
||||||
import MainMenu from '../../components/MainMenu';
|
import MainMenu from '../../components/MainMenu';
|
||||||
import FileUpload from './components/FileUpload';
|
import FileUpload from './components/FileUpload';
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import {
|
|||||||
UPLOAD_DOOR_LOCK_DATA_PENDING,
|
UPLOAD_DOOR_LOCK_DATA_PENDING,
|
||||||
UPLOAD_DOOR_LOCK_DATA_SUCCESS,
|
UPLOAD_DOOR_LOCK_DATA_SUCCESS,
|
||||||
UPLOAD_DOOR_LOCK_DATA_FAILED
|
UPLOAD_DOOR_LOCK_DATA_FAILED
|
||||||
} from "../constants";
|
} from '../constants';
|
||||||
|
|
||||||
import API from '../../utilities/api';
|
import API from '../../utilities/api';
|
||||||
|
|
||||||
|
|||||||
@@ -5,7 +5,10 @@ import {
|
|||||||
ADD_NEW_MAPPING_PENDING,
|
ADD_NEW_MAPPING_PENDING,
|
||||||
ADD_NEW_MAPPING_SUCCESS,
|
ADD_NEW_MAPPING_SUCCESS,
|
||||||
ADD_NEW_MAPPING_FAILED,
|
ADD_NEW_MAPPING_FAILED,
|
||||||
} from "../constants";
|
FETCH_INCIDENTS_PENDING,
|
||||||
|
FETCH_INCIDENTS_SUCCESS,
|
||||||
|
FETCH_INCIDENTS_FAILED,
|
||||||
|
} from '../constants';
|
||||||
|
|
||||||
import API from '../../utilities/api';
|
import API from '../../utilities/api';
|
||||||
|
|
||||||
@@ -32,3 +35,14 @@ export const addNewMapping = (dispatch, mapping) => {
|
|||||||
dispatch({type: ADD_NEW_MAPPING_FAILED, payload: error.response});
|
dispatch({type: ADD_NEW_MAPPING_FAILED, payload: error.response});
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const fetchIncidents = (dispatch) => {
|
||||||
|
dispatch({type: FETCH_INCIDENTS_PENDING});
|
||||||
|
API.get('integration/report/allIncidents')
|
||||||
|
.then(response => {
|
||||||
|
dispatch({type: FETCH_INCIDENTS_SUCCESS, payload: response.data});
|
||||||
|
})
|
||||||
|
.catch(error => {
|
||||||
|
dispatch({type: FETCH_INCIDENTS_FAILED, payload: error.response});
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|||||||
@@ -9,3 +9,7 @@ export const FETCH_MAPPINGS_FAILED = 'FETCH_MAPPINGS_FAILED';
|
|||||||
export const ADD_NEW_MAPPING_PENDING = 'ADD_NEW_MAPPING_PENDING';
|
export const ADD_NEW_MAPPING_PENDING = 'ADD_NEW_MAPPING_PENDING';
|
||||||
export const ADD_NEW_MAPPING_SUCCESS = 'ADD_NEW_MAPPING_SUCCESS';
|
export const ADD_NEW_MAPPING_SUCCESS = 'ADD_NEW_MAPPING_SUCCESS';
|
||||||
export const ADD_NEW_MAPPING_FAILED = 'ADD_NEW_MAPPING_FAILED';
|
export const ADD_NEW_MAPPING_FAILED = 'ADD_NEW_MAPPING_FAILED';
|
||||||
|
|
||||||
|
export const FETCH_INCIDENTS_PENDING = 'FETCH_INCIDENTS_PENDING';
|
||||||
|
export const FETCH_INCIDENTS_SUCCESS = 'FETCH_INCIDENTS_SUCCESS';
|
||||||
|
export const FETCH_INCIDENTS_FAILED = 'FETCH_INCIDENTS_FAILED';
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import {
|
|||||||
ADD_NEW_MAPPING_PENDING,
|
ADD_NEW_MAPPING_PENDING,
|
||||||
ADD_NEW_MAPPING_SUCCESS,
|
ADD_NEW_MAPPING_SUCCESS,
|
||||||
ADD_NEW_MAPPING_FAILED,
|
ADD_NEW_MAPPING_FAILED,
|
||||||
} from "../constants";
|
} from '../constants';
|
||||||
|
|
||||||
const initialState = {
|
const initialState = {
|
||||||
pending: false,
|
pending: false,
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import {
|
|||||||
UPLOAD_DOOR_LOCK_DATA_PENDING,
|
UPLOAD_DOOR_LOCK_DATA_PENDING,
|
||||||
UPLOAD_DOOR_LOCK_DATA_SUCCESS,
|
UPLOAD_DOOR_LOCK_DATA_SUCCESS,
|
||||||
UPLOAD_DOOR_LOCK_DATA_FAILED
|
UPLOAD_DOOR_LOCK_DATA_FAILED
|
||||||
} from "../constants";
|
} from '../constants';
|
||||||
|
|
||||||
const initialState = {
|
const initialState = {
|
||||||
pending: false,
|
pending: false,
|
||||||
|
|||||||
38
client/src/store/reducers/incidentsReportReducer.js
Normal file
38
client/src/store/reducers/incidentsReportReducer.js
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
import {
|
||||||
|
FETCH_INCIDENTS_PENDING,
|
||||||
|
FETCH_INCIDENTS_SUCCESS,
|
||||||
|
FETCH_INCIDENTS_FAILED,
|
||||||
|
} from '../constants';
|
||||||
|
|
||||||
|
const initialState = {
|
||||||
|
pending: false,
|
||||||
|
result: null,
|
||||||
|
error: null,
|
||||||
|
};
|
||||||
|
|
||||||
|
export const incidentsReport = (state, action) => {
|
||||||
|
state = state || initialState;
|
||||||
|
action = action || {};
|
||||||
|
|
||||||
|
switch(action.type){
|
||||||
|
case FETCH_INCIDENTS_PENDING:
|
||||||
|
return Object.assign({}, state, {
|
||||||
|
pending: true,
|
||||||
|
error: null,
|
||||||
|
});
|
||||||
|
case FETCH_INCIDENTS_SUCCESS:
|
||||||
|
return Object.assign({}, state, {
|
||||||
|
pending: false,
|
||||||
|
result: action.payload,
|
||||||
|
error: null,
|
||||||
|
});
|
||||||
|
case FETCH_INCIDENTS_FAILED:
|
||||||
|
return Object.assign({}, state, {
|
||||||
|
pending: false,
|
||||||
|
result: {},
|
||||||
|
error: action.payload,
|
||||||
|
});
|
||||||
|
default:
|
||||||
|
return state;
|
||||||
|
}
|
||||||
|
};
|
||||||
@@ -1,12 +1,14 @@
|
|||||||
import { combineReducers } from "redux";
|
import { combineReducers } from 'redux';
|
||||||
|
|
||||||
import { doorLockData} from "./doorLockReducers";
|
import { doorLockData} from './doorLockReducers';
|
||||||
import { mappingsData } from "./mappingsReducer";
|
import { mappingsData } from './mappingsReducer';
|
||||||
import { addMapping } from './addMappingReducer';
|
import { addMapping } from './addMappingReducer';
|
||||||
|
import { incidentsReport } from './incidentsReportReducer';
|
||||||
|
|
||||||
export const rootReducer = combineReducers({
|
export const rootReducer = combineReducers({
|
||||||
doorLockData,
|
doorLockData,
|
||||||
mappingsData,
|
mappingsData,
|
||||||
addMapping,
|
addMapping,
|
||||||
|
incidentsReport,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import {
|
|||||||
FETCH_MAPPINGS_PENDING,
|
FETCH_MAPPINGS_PENDING,
|
||||||
FETCH_MAPPINGS_SUCCESS,
|
FETCH_MAPPINGS_SUCCESS,
|
||||||
FETCH_MAPPINGS_FAILED,
|
FETCH_MAPPINGS_FAILED,
|
||||||
} from "../constants";
|
} from '../constants';
|
||||||
|
|
||||||
const initialState = {
|
const initialState = {
|
||||||
pending: false,
|
pending: false,
|
||||||
|
|||||||
@@ -2140,7 +2140,7 @@ class-utils@^0.3.5:
|
|||||||
isobject "^3.0.0"
|
isobject "^3.0.0"
|
||||||
static-extend "^0.1.1"
|
static-extend "^0.1.1"
|
||||||
|
|
||||||
classnames@^2.2.6:
|
classnames@^2.2.5, classnames@^2.2.6:
|
||||||
version "2.2.6"
|
version "2.2.6"
|
||||||
resolved "https://registry.yarnpkg.com/classnames/-/classnames-2.2.6.tgz#43935bffdd291f326dad0a205309b38d00f650ce"
|
resolved "https://registry.yarnpkg.com/classnames/-/classnames-2.2.6.tgz#43935bffdd291f326dad0a205309b38d00f650ce"
|
||||||
|
|
||||||
@@ -7095,6 +7095,12 @@ react-scripts@3.0.1:
|
|||||||
optionalDependencies:
|
optionalDependencies:
|
||||||
fsevents "2.0.6"
|
fsevents "2.0.6"
|
||||||
|
|
||||||
|
react-table@^6.10.0:
|
||||||
|
version "6.10.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/react-table/-/react-table-6.10.0.tgz#20444b19d8ca3c1a08e7544e5c3a93e4ba56690e"
|
||||||
|
dependencies:
|
||||||
|
classnames "^2.2.5"
|
||||||
|
|
||||||
react@^16.8.6:
|
react@^16.8.6:
|
||||||
version "16.8.6"
|
version "16.8.6"
|
||||||
resolved "https://registry.yarnpkg.com/react/-/react-16.8.6.tgz#ad6c3a9614fd3a4e9ef51117f54d888da01f2bbe"
|
resolved "https://registry.yarnpkg.com/react/-/react-16.8.6.tgz#ad6c3a9614fd3a4e9ef51117f54d888da01f2bbe"
|
||||||
|
|||||||
@@ -30,34 +30,39 @@ const uploadDoorLockData = (req, res) => {
|
|||||||
unknownMembers.push(...parserResult.unknownMembers);
|
unknownMembers.push(...parserResult.unknownMembers);
|
||||||
});
|
});
|
||||||
|
|
||||||
const asyncJobs = [];
|
const asyncWriteJobs = [];
|
||||||
|
|
||||||
fetchAllBookings()
|
parsedData.forEach((entry) => asyncWriteJobs.push(writeDoorLockEvent(entry)));
|
||||||
.then((bookingEntries) => {
|
|
||||||
bookingEntries.forEach((bookingEntry) => asyncJobs.push(writeBookingReservation(bookingEntry)));
|
|
||||||
})
|
|
||||||
.catch((error) => {
|
|
||||||
res.status(500).send(error);
|
|
||||||
return;
|
|
||||||
});
|
|
||||||
|
|
||||||
parsedData.forEach((entry) => asyncJobs.push(writeDoorLockEvent(entry)));
|
Promise.all(asyncWriteJobs)
|
||||||
|
|
||||||
Promise.all(asyncJobs)
|
|
||||||
.then(() => {
|
.then(() => {
|
||||||
res.json({
|
res.json({
|
||||||
parsedData,
|
parsedData,
|
||||||
parserErrors,
|
parserErrors,
|
||||||
unknownMembers
|
unknownMembers
|
||||||
});
|
});
|
||||||
|
|
||||||
calculateDoorLockCharges();
|
|
||||||
})
|
})
|
||||||
.catch((error) => {
|
.catch((error) => {
|
||||||
console.log(`${integrationServiceErrors.FAILED_TO_SAVE_BOOKINGS} or ${integrationServiceErrors.FAILED_TO_SAVE_DOOR_LOCK_ENTRIES}`)
|
console.log(integrationServiceErrors.FAILED_TO_SAVE_DOOR_LOCK_ENTRIES);
|
||||||
console.log(error);
|
console.log(error);
|
||||||
res.status(500).send(integrationServiceErrors.FAILED_TO_SAVE_DATA_GENERIC);
|
res.status(500).send(integrationServiceErrors.FAILED_TO_SAVE_DATA_GENERIC);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
fetchAllBookings()
|
||||||
|
.then((bookingEntries) => {
|
||||||
|
const asyncJobs = [];
|
||||||
|
bookingEntries.forEach((bookingEntry) => asyncJobs.push(writeBookingReservation(bookingEntry)));
|
||||||
|
|
||||||
|
Promise.all(asyncJobs)
|
||||||
|
.then(() => {
|
||||||
|
calculateDoorLockCharges();
|
||||||
|
});
|
||||||
|
})
|
||||||
|
.catch((error) => {
|
||||||
|
console.log(integrationServiceErrors.FAILED_TO_SAVE_BOOKINGS);
|
||||||
|
console.log(error);
|
||||||
|
return;
|
||||||
|
});
|
||||||
})
|
})
|
||||||
.catch((error) => {
|
.catch((error) => {
|
||||||
res.status(500).send(error);
|
res.status(500).send(error);
|
||||||
|
|||||||
@@ -1,6 +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 { getAllDoorLockIncidents } = require('../services/integration/reports');
|
||||||
|
|
||||||
const getKnownOfficeResourceMappings = (req, res) => {
|
const getKnownOfficeResourceMappings = (req, res) => {
|
||||||
const dataToFetch = [getMappingsFromDatabase(), fetchOffices(), fetchResources() ];
|
const dataToFetch = [getMappingsFromDatabase(), fetchOffices(), fetchResources() ];
|
||||||
@@ -31,7 +32,29 @@ const addNewMapping = (req, res) => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const getAllIncidents = (req, res) => {
|
||||||
|
getAllDoorLockIncidents()
|
||||||
|
.then((incidents) => {
|
||||||
|
res.send(incidents);
|
||||||
|
})
|
||||||
|
.catch((error) => {
|
||||||
|
console.log(error);
|
||||||
|
res.send([]);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const getUnlockedIncidents = (req, res) => {
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
const getUnscheduledIncidents = (req, res) => {
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
getKnownOfficeResourceMappings,
|
getKnownOfficeResourceMappings,
|
||||||
addNewMapping,
|
addNewMapping,
|
||||||
|
getAllIncidents,
|
||||||
|
getUnscheduledIncidents,
|
||||||
|
getUnlockedIncidents,
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -3,7 +3,9 @@ BASIC_AUTH_PASSWORD=password
|
|||||||
|
|
||||||
OFFICE_RnD_TOKEN=token for Office RnD API requests
|
OFFICE_RnD_TOKEN=token for Office RnD API requests
|
||||||
MAX_BACK_TO_BACK_DIFFERENCE=Time in minutes
|
MAX_BACK_TO_BACK_DIFFERENCE=Time in minutes
|
||||||
EARLIEST_UNLOCK=2
|
EARLIEST_UNLOCK=Time in minutes
|
||||||
|
|
||||||
|
UI_TIMEZONE=Timezone for user interface | https://en.wikipedia.org/wiki/List_of_tz_database_time_zones | example : America/Los_Angeles
|
||||||
|
|
||||||
UNSCHEDULED_USE_TIME_RESOLUTION=Time in minutes
|
UNSCHEDULED_USE_TIME_RESOLUTION=Time in minutes
|
||||||
UNSCHEDULED_USE_CHARGE_FEE=Charge fee
|
UNSCHEDULED_USE_CHARGE_FEE=Charge fee
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
const { apiStatusCheck } = require('../controllers/apiStatusCheck');
|
const { apiStatusCheck } = require('../controllers/apiStatusCheck');
|
||||||
const { uploadDoorLockData } = require('../controllers/doorLock');
|
const { uploadDoorLockData } = require('../controllers/doorLock');
|
||||||
const { getKnownOfficeResourceMappings, addNewMapping } = require('../controllers/integration');
|
const { getKnownOfficeResourceMappings, addNewMapping, getAllIncidents,getUnscheduledIncidents, getUnlockedIncidents } = require('../controllers/integration');
|
||||||
const { calculateDoorLockCharges } = require('../services/integration/doorLockCharges');
|
const { calculateDoorLockCharges } = require('../services/integration/doorLockCharges');
|
||||||
|
|
||||||
const express = require('express');
|
const express = require('express');
|
||||||
@@ -14,6 +14,10 @@ router.post('/doorLock/upload', uploadDoorLockData);
|
|||||||
router.get('/integration/mappings', getKnownOfficeResourceMappings);
|
router.get('/integration/mappings', getKnownOfficeResourceMappings);
|
||||||
router.post('/integration/mappings', addNewMapping);
|
router.post('/integration/mappings', addNewMapping);
|
||||||
|
|
||||||
|
router.get('/integration/report/allIncidents', getAllIncidents);
|
||||||
|
router.get('/integration/report/unlockedIncidents', getUnlockedIncidents);
|
||||||
|
router.get('/integration/report/unscheduledIncidents', getUnscheduledIncidents);
|
||||||
|
|
||||||
// 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();});
|
||||||
|
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ const {
|
|||||||
csvParserErrors,
|
csvParserErrors,
|
||||||
} = require('../../constants/constants');
|
} = require('../../constants/constants');
|
||||||
|
|
||||||
const { fetchAllMembers, findMember } = require('../officeRnD/members');
|
const { fetchAllMembers } = require('../officeRnD/members');
|
||||||
const { getMappingsFromDatabase } = require('../officeRnD/resources');
|
const { getMappingsFromDatabase } = require('../officeRnD/resources');
|
||||||
|
|
||||||
const extractMappingFromFileName = (fileName) => {
|
const extractMappingFromFileName = (fileName) => {
|
||||||
@@ -46,6 +46,7 @@ const parseDoorLockDataFile = (file) => {
|
|||||||
Promise.all(prefetchDataJobs)
|
Promise.all(prefetchDataJobs)
|
||||||
.then(result => {
|
.then(result => {
|
||||||
const mappings = result[0];
|
const mappings = result[0];
|
||||||
|
const allMembers = result[1];
|
||||||
|
|
||||||
const mappingFromFileName = extractMappingFromFileName(file.name);
|
const mappingFromFileName = extractMappingFromFileName(file.name);
|
||||||
const mappingObject = checkIfMappingExsists(mappingFromFileName, mappings);
|
const mappingObject = checkIfMappingExsists(mappingFromFileName, mappings);
|
||||||
@@ -103,7 +104,7 @@ const parseDoorLockDataFile = (file) => {
|
|||||||
const secondEntry = results[i+1];
|
const secondEntry = results[i+1];
|
||||||
|
|
||||||
if (firstEntry && (firstEntry.event === USER_ENTRY_EVENT)){
|
if (firstEntry && (firstEntry.event === USER_ENTRY_EVENT)){
|
||||||
const memberObject = findMember(firstEntry.name);
|
const memberObject = allMembers.find(member => member.name === firstEntry.name);
|
||||||
|
|
||||||
if (!memberObject){
|
if (!memberObject){
|
||||||
//Check if member is already labeled as unknown
|
//Check if member is already labeled as unknown
|
||||||
|
|||||||
@@ -97,8 +97,8 @@ const createUnlockedIncident = (reservation) => {
|
|||||||
|
|
||||||
const createUnscheduledUseIncident = (reservation, doorLockEntry) => {
|
const createUnscheduledUseIncident = (reservation, doorLockEntry) => {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
const timeResolution = parseInt(process.env.UNSCHEDULED_USE_TIME_RESOLUTION);
|
const timeResolution = parseInt(process.env.UNSCHEDULED_USE_TIME_RESOLUTION) || 5;
|
||||||
const chargePrice = parseFloat(process.env.UNSCHEDULED_USE_CHARGE_FEE);
|
const chargePrice = parseFloat(process.env.UNSCHEDULED_USE_CHARGE_FEE) || 5;
|
||||||
|
|
||||||
const reservationEndTime = moment(reservation.end);
|
const reservationEndTime = moment(reservation.end);
|
||||||
const lockedTime = moment(doorLockEntry.timestamp);
|
const lockedTime = moment(doorLockEntry.timestamp);
|
||||||
@@ -352,8 +352,8 @@ const getIncidentData = (reservation) => {
|
|||||||
getRelatedDoorLockEntries(reservation.start, doorLockEntriesEndTime, reservation.memberId, reservation.resourceId)
|
getRelatedDoorLockEntries(reservation.start, doorLockEntriesEndTime, reservation.memberId, reservation.resourceId)
|
||||||
.then((lockEntry) => {
|
.then((lockEntry) => {
|
||||||
if (lockEntry){
|
if (lockEntry){
|
||||||
const timeResolution = parseInt(process.env.UNSCHEDULED_USE_TIME_RESOLUTION);
|
const timeResolution = parseInt(process.env.UNSCHEDULED_USE_TIME_RESOLUTION) || 5
|
||||||
const chargePrice = parseFloat(process.env.UNSCHEDULED_USE_CHARGE_FEE);
|
const chargePrice = parseFloat(process.env.UNSCHEDULED_USE_CHARGE_FEE) || 5;
|
||||||
|
|
||||||
const reservationEndTime = moment(reservation.end);
|
const reservationEndTime = moment(reservation.end);
|
||||||
const lockedTime = moment(lockEntry.timestamp);
|
const lockedTime = moment(lockEntry.timestamp);
|
||||||
|
|||||||
112
services/integration/reports.js
Normal file
112
services/integration/reports.js
Normal file
@@ -0,0 +1,112 @@
|
|||||||
|
'use strict';
|
||||||
|
|
||||||
|
const moment = require('moment-timezone');
|
||||||
|
|
||||||
|
const db = require('../../models/index');
|
||||||
|
const { incidentType } = require('../../constants/constants');
|
||||||
|
|
||||||
|
const { fetchAllMembers } = require('../officeRnD/members');
|
||||||
|
const { fetchOffices, fetchResources } = require('../officeRnD/resources');
|
||||||
|
|
||||||
|
const getUnlockedIncidents = () => {
|
||||||
|
const attributes = ['id', 'memberId', 'resourceId', 'bookingStart', 'bookingEnd', 'incidentLevel', 'incidentLevelPrice'];
|
||||||
|
|
||||||
|
return db.unlockedIncident.findAll({
|
||||||
|
attributes,
|
||||||
|
sort: [
|
||||||
|
['bookingStart', 'ASC']
|
||||||
|
]
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const getUnscheduledIncidents = () => {
|
||||||
|
const attributes = [
|
||||||
|
'id',
|
||||||
|
'memberId',
|
||||||
|
'resourceId',
|
||||||
|
'bookingStart',
|
||||||
|
'bookingEnd',
|
||||||
|
'doorLockEventTimestamp',
|
||||||
|
'doorLockEventType',
|
||||||
|
'timeIntervalsToCharge',
|
||||||
|
'chargePrice',
|
||||||
|
'totalChargeFee'
|
||||||
|
];
|
||||||
|
|
||||||
|
return db.unscheduledIncident.findAll({
|
||||||
|
attributes,
|
||||||
|
sort: [
|
||||||
|
['bookingStart', 'ASC']
|
||||||
|
]
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const formatTime = (timestamp) => {
|
||||||
|
const timezone = process.env.UI_TIMEZONE || 'America/Los_Angeles';
|
||||||
|
return moment.tz(timestamp, timezone).format('MM/DD/YYYY hh:mm a');
|
||||||
|
};
|
||||||
|
|
||||||
|
const getAllDoorLockIncidents = () => {
|
||||||
|
return new Promise ((resolve, reject) => {
|
||||||
|
const dataFetchJobs = [fetchAllMembers(), fetchOffices(), fetchResources(), getUnlockedIncidents(), getUnscheduledIncidents()];
|
||||||
|
|
||||||
|
Promise.all(dataFetchJobs)
|
||||||
|
.then((data) => {
|
||||||
|
const members = data[0];
|
||||||
|
const offices = data[1];
|
||||||
|
const resources = data[2];
|
||||||
|
const unlockedIncidents = data[3];
|
||||||
|
const unscheduledIncidents = data[4];
|
||||||
|
|
||||||
|
const membersMap = {};
|
||||||
|
const officesMap = {};
|
||||||
|
const resourcesMap = {};
|
||||||
|
|
||||||
|
members.forEach((member) => membersMap[member.memberId] = member);
|
||||||
|
offices.forEach((office) => officesMap[office.officeId] = office);
|
||||||
|
resources.forEach((resource) => resourcesMap[resource.resourceId] = resource);
|
||||||
|
|
||||||
|
const allIncidents = [];
|
||||||
|
|
||||||
|
unlockedIncidents.forEach((unlockedIncident) => {
|
||||||
|
allIncidents.push({
|
||||||
|
incidentId: unlockedIncident.id,
|
||||||
|
memberId: unlockedIncident.memberId,
|
||||||
|
memberName: membersMap[unlockedIncident.memberId].name,
|
||||||
|
resourceName: resourcesMap[unlockedIncident.resourceId].resourceName,
|
||||||
|
officeName: officesMap[resourcesMap[unlockedIncident.resourceId].officeId].officeName,
|
||||||
|
bookingStart: formatTime(unlockedIncident.bookingStart),
|
||||||
|
bookingEnd: formatTime(unlockedIncident.bookingEnd),
|
||||||
|
incidentType: incidentType.UNLOCKED_INCIDENT,
|
||||||
|
incidentLevel: unlockedIncident.incidentLevel,
|
||||||
|
incidentPrice: unlockedIncident.incidentLevelPrice,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
unscheduledIncidents.forEach((unscheduledIncident) => {
|
||||||
|
allIncidents.push({
|
||||||
|
incidentId: unscheduledIncident.id,
|
||||||
|
memberId: unscheduledIncident.memberId,
|
||||||
|
memberName: membersMap[unscheduledIncident.memberId].name,
|
||||||
|
resourceName: resourcesMap[unscheduledIncident.resourceId].resourceName,
|
||||||
|
officeName: officesMap[resourcesMap[unscheduledIncident.resourceId].officeId].officeName,
|
||||||
|
bookingStart: formatTime(unscheduledIncident.bookingStart),
|
||||||
|
bookingEnd: formatTime(unscheduledIncident.bookingEnd),
|
||||||
|
incidentType: incidentType.UNSCHEDULED_INCIDENT,
|
||||||
|
timeIntervalsToCharge: unscheduledIncident.timeIntervalsToCharge,
|
||||||
|
chargePrice: unscheduledIncident.chargePrice,
|
||||||
|
totalChargeFee: unscheduledIncident.totalChargeFee,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
resolve(allIncidents);
|
||||||
|
})
|
||||||
|
.catch((error) => reject(error));
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
getUnlockedIncidents,
|
||||||
|
getUnscheduledIncidents,
|
||||||
|
getAllDoorLockIncidents,
|
||||||
|
};
|
||||||
@@ -2,20 +2,19 @@
|
|||||||
|
|
||||||
const { API } = require('../../helpers/api');
|
const { API } = require('../../helpers/api');
|
||||||
|
|
||||||
const membersList = [];
|
|
||||||
|
|
||||||
const fetchAllMembers = () => {
|
const fetchAllMembers = () => {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
API.get('/members')
|
API.get('/members')
|
||||||
.then((result) => {
|
.then((result) => {
|
||||||
|
const cleanedResult = [];
|
||||||
const members = result.data || [];
|
const members = result.data || [];
|
||||||
members.forEach((member) => {
|
members.forEach((member) => {
|
||||||
membersList.push({
|
cleanedResult.push({
|
||||||
name: member.name,
|
name: member.name,
|
||||||
memberId: member['_id'],
|
memberId: member['_id'],
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
resolve();
|
resolve(cleanedResult);
|
||||||
})
|
})
|
||||||
.catch((error) => {
|
.catch((error) => {
|
||||||
reject(error);
|
reject(error);
|
||||||
@@ -23,11 +22,6 @@ const fetchAllMembers = () => {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
const findMember = (memberName) => {
|
|
||||||
return membersList.find((member) => member.name === memberName);
|
|
||||||
};
|
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
fetchAllMembers,
|
fetchAllMembers,
|
||||||
findMember,
|
|
||||||
};
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user