Fix for loading
This commit is contained in:
53
client/src/components/GenerateFeesInORDButton/index.js
Normal file
53
client/src/components/GenerateFeesInORDButton/index.js
Normal file
@@ -0,0 +1,53 @@
|
|||||||
|
import React, { Component } from 'react';
|
||||||
|
import { connect } from 'react-redux';
|
||||||
|
import { Button, Modal } from 'semantic-ui-react';
|
||||||
|
|
||||||
|
import { addFeesToOrd } from '../../store/actions';
|
||||||
|
|
||||||
|
class GenerateFeesInORDButton extends Component {
|
||||||
|
state = { open: false };
|
||||||
|
|
||||||
|
show = size => () => this.setState({ size, open: true });
|
||||||
|
close = () => this.setState({ open: false });
|
||||||
|
confirm = () => {
|
||||||
|
const { addFeesToOrd, dateRange, memberIds } = this.props;
|
||||||
|
|
||||||
|
if (dateRange){
|
||||||
|
addFeesToOrd(dateRange, memberIds);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.close();
|
||||||
|
};
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const { open, size } = this.state;
|
||||||
|
const { singleMember, disabled } = this.props;
|
||||||
|
|
||||||
|
const modalContent = singleMember ?
|
||||||
|
'This will remove all existing fees in ORD for selected member in selected date range and generate new fees based on shown incident tables. Do you want to continue ?':
|
||||||
|
'This will remove all existing fees in ORD for all members in selected date range and generate new fees based on shown incident tables. Do you want to continue ?';
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<Button disabled={disabled} onClick={this.show('tiny')}>Generate fees in ORD</Button>
|
||||||
|
|
||||||
|
<Modal size={size} open={open} onClose={this.close}>
|
||||||
|
<Modal.Header>Add fees to the ORD</Modal.Header>
|
||||||
|
<Modal.Content>
|
||||||
|
<p>{modalContent}</p>
|
||||||
|
</Modal.Content>
|
||||||
|
<Modal.Actions>
|
||||||
|
<Button negative onClick={this.close}>No</Button>
|
||||||
|
<Button positive icon='checkmark' onClick={this.confirm} labelPosition='right' content='Yes' />
|
||||||
|
</Modal.Actions>
|
||||||
|
</Modal>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const mapDispatchToProps = (dispatch) => ({
|
||||||
|
addFeesToOrd: (dateRange, memberIds) => addFeesToOrd(dispatch, dateRange, memberIds),
|
||||||
|
});
|
||||||
|
|
||||||
|
export default connect(null, mapDispatchToProps)(GenerateFeesInORDButton);
|
||||||
@@ -1,111 +0,0 @@
|
|||||||
import React from 'react';
|
|
||||||
import { Loader } from 'semantic-ui-react';
|
|
||||||
import ReactTable from 'react-table';
|
|
||||||
import 'react-table/react-table.css';
|
|
||||||
import { NavLink } from 'react-router-dom';
|
|
||||||
|
|
||||||
import {incidentsReportHeaderTitles} from '../../constants/menuItems';
|
|
||||||
import {
|
|
||||||
incidentDescriptions,
|
|
||||||
incidentLevelDescriptions,
|
|
||||||
UNLOCKED_INCIDENT,
|
|
||||||
UNSCHEDULED_INCIDENT
|
|
||||||
} from '../../constants/enums';
|
|
||||||
|
|
||||||
|
|
||||||
const MemberIncidentsTable = props => {
|
|
||||||
const { loading, title, openMemberSummaryOnMemberClick } = props;
|
|
||||||
const incidents = props.incidents ? props.incidents : [];
|
|
||||||
|
|
||||||
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 = '';
|
|
||||||
let urlValue = undefined;
|
|
||||||
|
|
||||||
switch (props.column.id) {
|
|
||||||
case 'memberName':
|
|
||||||
const memberId = props.row['_original'].memberId;
|
|
||||||
urlValue = `/practice-summary-report/${memberId}`;
|
|
||||||
cellValue = props.value;
|
|
||||||
break;
|
|
||||||
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;
|
|
||||||
default:
|
|
||||||
cellValue = '';
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (openMemberSummaryOnMemberClick && urlValue){
|
|
||||||
return <NavLink to={urlValue}>{cellValue}</NavLink>
|
|
||||||
}else{
|
|
||||||
return <div style={{ textAlign: columnContentsAlignment }}>{cellValue}</div>
|
|
||||||
}
|
|
||||||
|
|
||||||
// return <NavLink to={urlValue}>
|
|
||||||
// <div>{cellValue}</div>
|
|
||||||
// </NavLink>
|
|
||||||
|
|
||||||
// return <div style={{ textAlign: columnContentsAlignment }}><a href={'www.gogole.com'} >{cellValue}</a></div>
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div>
|
|
||||||
<h4>{title}</h4>
|
|
||||||
<Loader active={loading} />
|
|
||||||
{
|
|
||||||
!loading && incidents &&
|
|
||||||
<ReactTable
|
|
||||||
data={incidents}
|
|
||||||
multiSort={false}
|
|
||||||
columns={columns}
|
|
||||||
/>
|
|
||||||
}
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default MemberIncidentsTable;
|
|
||||||
@@ -5,26 +5,50 @@ import { Container } from 'semantic-ui-react';
|
|||||||
import MainMenu from '../../components/MainMenu';
|
import MainMenu from '../../components/MainMenu';
|
||||||
import DateRangePicker from '../../components/DateRangePicker';
|
import DateRangePicker from '../../components/DateRangePicker';
|
||||||
import MemberIncidentsTables from '../../components/MemberIncidentsTables';
|
import MemberIncidentsTables from '../../components/MemberIncidentsTables';
|
||||||
|
import GenerateFeesInORDButton from '../../components/GenerateFeesInORDButton';
|
||||||
|
|
||||||
import { fetchIncidents } from '../../store/actions';
|
import { fetchIncidents, addFeesToOrd } from '../../store/actions';
|
||||||
|
|
||||||
class IncidentsReport extends Component {
|
class IncidentsReport extends Component {
|
||||||
onDatesUpdate(dateRange) {
|
state = {dateRange: null};
|
||||||
|
|
||||||
|
onDatesUpdate = (dateRange) => {
|
||||||
const { fetchIncidents } = this.props;
|
const { fetchIncidents } = this.props;
|
||||||
|
|
||||||
|
this.setState({dateRange});
|
||||||
fetchIncidents(dateRange);
|
fetchIncidents(dateRange);
|
||||||
}
|
};
|
||||||
|
|
||||||
render () {
|
render () {
|
||||||
const { pendingIncidents, incidents } = this.props;
|
const { pendingIncidents, incidents, pendingAddFeesStatus } = this.props;
|
||||||
|
const { dateRange } = this.state;
|
||||||
|
|
||||||
|
const loading = pendingIncidents || pendingAddFeesStatus;
|
||||||
|
|
||||||
|
const membersMap = {};
|
||||||
|
if (incidents && Array.isArray(incidents)) {
|
||||||
|
incidents.forEach((incident) => {
|
||||||
|
membersMap[incident.memberId] = true;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
const memberIds = Object.keys(membersMap) || [];
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Container>
|
<Container>
|
||||||
<MainMenu/>
|
<MainMenu/>
|
||||||
<h3>Incidents Report</h3>
|
<h3>Incidents Report</h3>
|
||||||
<hr/>
|
<hr/>
|
||||||
<DateRangePicker buttonLabel="Show report" onDatesUpdate={this.onDatesUpdate.bind(this)} />
|
<DateRangePicker buttonLabel="Show report" onDatesUpdate={this.onDatesUpdate} inlineButton />
|
||||||
<br/>
|
<br/>
|
||||||
<MemberIncidentsTables pendingIncidents={pendingIncidents} incidents={incidents} />
|
<GenerateFeesInORDButton
|
||||||
|
memberIds={memberIds}
|
||||||
|
disabled={loading}
|
||||||
|
dateRange={dateRange}
|
||||||
|
/>
|
||||||
|
<br/><br/>
|
||||||
|
<hr/>
|
||||||
|
<br/>
|
||||||
|
<MemberIncidentsTables pendingIncidents={loading} incidents={incidents} />
|
||||||
</Container>
|
</Container>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -33,10 +57,12 @@ class IncidentsReport extends Component {
|
|||||||
const mapStateToProps = (state) => ({
|
const mapStateToProps = (state) => ({
|
||||||
pendingIncidents: state.incidentsReport.pending,
|
pendingIncidents: state.incidentsReport.pending,
|
||||||
incidents: state.incidentsReport.result,
|
incidents: state.incidentsReport.result,
|
||||||
|
pendingAddFeesStatus: state.addFeesStatus.pending,
|
||||||
});
|
});
|
||||||
|
|
||||||
const mapDispatchToProps = (dispatch) => ({
|
const mapDispatchToProps = (dispatch) => ({
|
||||||
fetchIncidents: (dateRange) => fetchIncidents(dispatch, dateRange),
|
fetchIncidents: (dateRange) => fetchIncidents(dispatch, dateRange),
|
||||||
|
addFeesToOrd: (dateRange, memberIds) => addFeesToOrd(dispatch, dateRange, memberIds),
|
||||||
});
|
});
|
||||||
|
|
||||||
export default connect(mapStateToProps, mapDispatchToProps)(IncidentsReport);
|
export default connect(mapStateToProps, mapDispatchToProps)(IncidentsReport);
|
||||||
|
|||||||
@@ -1,12 +1,13 @@
|
|||||||
import React, { Component } from 'react';
|
import React, { Component } from 'react';
|
||||||
import { connect } from 'react-redux';
|
import { connect } from 'react-redux';
|
||||||
import {Container, Grid} from 'semantic-ui-react';
|
import { Container, Grid } from 'semantic-ui-react';
|
||||||
|
|
||||||
import MainMenu from '../../components/MainMenu';
|
import MainMenu from '../../components/MainMenu';
|
||||||
import DateRangePicker from '../../components/DateRangePicker';
|
import DateRangePicker from '../../components/DateRangePicker';
|
||||||
import MemberSelector from './components/MemberSelector';
|
import MemberSelector from './components/MemberSelector';
|
||||||
import MemberSummary from './components/MemberSummary';
|
import MemberSummary from './components/MemberSummary';
|
||||||
import MemberIncidentsTables from '../../components/MemberIncidentsTables';
|
import MemberIncidentsTables from '../../components/MemberIncidentsTables';
|
||||||
|
import GenerateFeesInORDButton from '../../components/GenerateFeesInORDButton';
|
||||||
|
|
||||||
import { fetchMemberIncidents } from '../../store/actions';
|
import { fetchMemberIncidents } from '../../store/actions';
|
||||||
|
|
||||||
@@ -39,8 +40,12 @@ class PracticeSummaryReport extends Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
render () {
|
render () {
|
||||||
const { memberIncidents, loading } = this.props;
|
const { memberIncidents, loadingMemberIncidents, loadingAddFeesStatus } = this.props;
|
||||||
const { memberId } = this.state;
|
const { memberId, dateRange } = this.state;
|
||||||
|
|
||||||
|
const loading = loadingAddFeesStatus || loadingMemberIncidents;
|
||||||
|
|
||||||
|
const addFeesButtonDisabled = !memberId || !dateRange || loading;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Container>
|
<Container>
|
||||||
@@ -64,6 +69,16 @@ class PracticeSummaryReport extends Component {
|
|||||||
/>
|
/>
|
||||||
</Grid.Column>
|
</Grid.Column>
|
||||||
</Grid.Row>
|
</Grid.Row>
|
||||||
|
<Grid.Row>
|
||||||
|
<Grid.Column>
|
||||||
|
<GenerateFeesInORDButton
|
||||||
|
singleMember
|
||||||
|
disabled={addFeesButtonDisabled}
|
||||||
|
dateRange={dateRange}
|
||||||
|
memberIds={[memberId]}
|
||||||
|
/>
|
||||||
|
</Grid.Column>
|
||||||
|
</Grid.Row>
|
||||||
<Grid.Row/>
|
<Grid.Row/>
|
||||||
<Grid.Row>
|
<Grid.Row>
|
||||||
<Grid.Column>
|
<Grid.Column>
|
||||||
@@ -78,7 +93,8 @@ class PracticeSummaryReport extends Component {
|
|||||||
|
|
||||||
const mapStateToProps = (state) => ({
|
const mapStateToProps = (state) => ({
|
||||||
memberIncidents: state.memberIncidents.result,
|
memberIncidents: state.memberIncidents.result,
|
||||||
loading: state.memberIncidents.pending,
|
loadingMemberIncidents: state.memberIncidents.pending,
|
||||||
|
loadingAddFeesStatus: state.addFeesStatus.pending,
|
||||||
});
|
});
|
||||||
|
|
||||||
const mapDispatchToProps = (dispatch) => ({
|
const mapDispatchToProps = (dispatch) => ({
|
||||||
|
|||||||
@@ -14,6 +14,9 @@ import {
|
|||||||
FETCH_MEMBER_INCIDENTS_PENDING,
|
FETCH_MEMBER_INCIDENTS_PENDING,
|
||||||
FETCH_MEMBER_INCIDENTS_SUCCESS,
|
FETCH_MEMBER_INCIDENTS_SUCCESS,
|
||||||
FETCH_MEMBER_INCIDENTS_FAILED,
|
FETCH_MEMBER_INCIDENTS_FAILED,
|
||||||
|
ADD_FEES_TO_ORD_PENDING,
|
||||||
|
ADD_FEES_TO_ORD_SUCCESS,
|
||||||
|
ADD_FEES_TO_ORD_FAILED,
|
||||||
} from '../constants';
|
} from '../constants';
|
||||||
|
|
||||||
import API from '../../utilities/api';
|
import API from '../../utilities/api';
|
||||||
@@ -78,3 +81,17 @@ export const fetchMemberIncidents = (dispatch, memberId, dateRange) => {
|
|||||||
dispatch({type: FETCH_MEMBER_INCIDENTS_FAILED, payload: error.response});
|
dispatch({type: FETCH_MEMBER_INCIDENTS_FAILED, payload: error.response});
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const addFeesToOrd = (dispatch, dateRange, memberIds) => {
|
||||||
|
dispatch({type: ADD_FEES_TO_ORD_PENDING});
|
||||||
|
API.post(`integration/addFees`, {
|
||||||
|
dateRange,
|
||||||
|
memberIds: memberIds || [],
|
||||||
|
})
|
||||||
|
.then(response => {
|
||||||
|
dispatch({type: ADD_FEES_TO_ORD_SUCCESS, payload: response.data});
|
||||||
|
})
|
||||||
|
.catch(error => {
|
||||||
|
dispatch({type: ADD_FEES_TO_ORD_FAILED, payload: error.response});
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|||||||
@@ -21,3 +21,7 @@ export const FETCH_MEMBERS_FAILED = 'FETCH_MEMBERS_FAILED';
|
|||||||
export const FETCH_MEMBER_INCIDENTS_PENDING = 'FETCH_MEMBER_INCIDENTS_PENDING';
|
export const FETCH_MEMBER_INCIDENTS_PENDING = 'FETCH_MEMBER_INCIDENTS_PENDING';
|
||||||
export const FETCH_MEMBER_INCIDENTS_SUCCESS = 'FETCH_MEMBER_INCIDENTS_SUCCESS';
|
export const FETCH_MEMBER_INCIDENTS_SUCCESS = 'FETCH_MEMBER_INCIDENTS_SUCCESS';
|
||||||
export const FETCH_MEMBER_INCIDENTS_FAILED = 'FETCH_MEMBER_INCIDENTS_FAILED';
|
export const FETCH_MEMBER_INCIDENTS_FAILED = 'FETCH_MEMBER_INCIDENTS_FAILED';
|
||||||
|
|
||||||
|
export const ADD_FEES_TO_ORD_PENDING = 'ADD_FEES_TO_ORD_PENDING';
|
||||||
|
export const ADD_FEES_TO_ORD_SUCCESS = 'ADD_FEES_TO_ORD_SUCCESS';
|
||||||
|
export const ADD_FEES_TO_ORD_FAILED = 'ADD_FEES_TO_ORD_FAILED';
|
||||||
|
|||||||
38
client/src/store/reducers/addFeesToOrdReducer.js
Normal file
38
client/src/store/reducers/addFeesToOrdReducer.js
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
import {
|
||||||
|
ADD_FEES_TO_ORD_PENDING,
|
||||||
|
ADD_FEES_TO_ORD_SUCCESS,
|
||||||
|
ADD_FEES_TO_ORD_FAILED,
|
||||||
|
} from '../constants';
|
||||||
|
|
||||||
|
const initialState = {
|
||||||
|
pending: false,
|
||||||
|
result: null,
|
||||||
|
error: null,
|
||||||
|
};
|
||||||
|
|
||||||
|
export const addFeesStatus = (state, action) => {
|
||||||
|
state = state || initialState;
|
||||||
|
action = action || {};
|
||||||
|
|
||||||
|
switch(action.type){
|
||||||
|
case ADD_FEES_TO_ORD_PENDING:
|
||||||
|
return Object.assign({}, state, {
|
||||||
|
pending: true,
|
||||||
|
error: null,
|
||||||
|
});
|
||||||
|
case ADD_FEES_TO_ORD_SUCCESS:
|
||||||
|
return Object.assign({}, state, {
|
||||||
|
pending: false,
|
||||||
|
result: action.payload,
|
||||||
|
error: null,
|
||||||
|
});
|
||||||
|
case ADD_FEES_TO_ORD_FAILED:
|
||||||
|
return Object.assign({}, state, {
|
||||||
|
pending: false,
|
||||||
|
result: {},
|
||||||
|
error: action.payload,
|
||||||
|
});
|
||||||
|
default:
|
||||||
|
return state;
|
||||||
|
}
|
||||||
|
};
|
||||||
@@ -6,6 +6,7 @@ import { addMapping } from './addMappingReducer';
|
|||||||
import { incidentsReport } from './incidentsReportReducer';
|
import { incidentsReport } from './incidentsReportReducer';
|
||||||
import { membersList } from './membersListReducer';
|
import { membersList } from './membersListReducer';
|
||||||
import { memberIncidents} from './memberIncidentsReducer';
|
import { memberIncidents} from './memberIncidentsReducer';
|
||||||
|
import { addFeesStatus } from './addFeesToOrdReducer';
|
||||||
|
|
||||||
export const rootReducer = combineReducers({
|
export const rootReducer = combineReducers({
|
||||||
doorLockData,
|
doorLockData,
|
||||||
@@ -14,5 +15,6 @@ export const rootReducer = combineReducers({
|
|||||||
incidentsReport,
|
incidentsReport,
|
||||||
membersList,
|
membersList,
|
||||||
memberIncidents,
|
memberIncidents,
|
||||||
|
addFeesStatus,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -1,3 +1,9 @@
|
|||||||
|
const pool = {
|
||||||
|
max: parseInt(process.env.DB_POOL_MAX_CONNECTIONS) || 5,
|
||||||
|
acquire: parseInt(process.env.DB_POOL_ACQUIRE) || 60000,
|
||||||
|
evict: parseInt(process.env.DB_POOL_EVICT) || 1000,
|
||||||
|
};
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
development: {
|
development: {
|
||||||
username: 'docker',
|
username: 'docker',
|
||||||
@@ -5,14 +11,17 @@ module.exports = {
|
|||||||
database: 'CrmIntegration',
|
database: 'CrmIntegration',
|
||||||
port: '5431',
|
port: '5431',
|
||||||
dialect: 'postgres',
|
dialect: 'postgres',
|
||||||
logging: parseInt(process.env.SEQUELIZE_LOGGING) ? console.log : false
|
logging: parseInt(process.env.SEQUELIZE_LOGGING) ? console.log : false,
|
||||||
|
pool
|
||||||
},
|
},
|
||||||
test: {
|
test: {
|
||||||
"use_env_variable": 'DATABASE_URL',
|
"use_env_variable": 'DATABASE_URL',
|
||||||
logging: parseInt(process.env.SEQUELIZE_LOGGING) ? console.log : false
|
logging: parseInt(process.env.SEQUELIZE_LOGGING) ? console.log : false,
|
||||||
|
pool
|
||||||
},
|
},
|
||||||
production: {
|
production: {
|
||||||
"use_env_variable": 'DATABASE_URL',
|
"use_env_variable": 'DATABASE_URL',
|
||||||
logging: parseInt(process.env.SEQUELIZE_LOGGING) ? console.log : false
|
logging: parseInt(process.env.SEQUELIZE_LOGGING) ? console.log : false,
|
||||||
|
pool
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,16 +0,0 @@
|
|||||||
{
|
|
||||||
"development": {
|
|
||||||
"username": "docker",
|
|
||||||
"password": "docker",
|
|
||||||
"database": "CrmIntegration",
|
|
||||||
"port": "5431",
|
|
||||||
"dialect": "postgres",
|
|
||||||
"logging": false
|
|
||||||
},
|
|
||||||
"test": {
|
|
||||||
"use_env_variable": "DATABASE_URL"
|
|
||||||
},
|
|
||||||
"production": {
|
|
||||||
"use_env_variable": "DATABASE_URL"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -13,32 +13,38 @@ const unlockedIncidentLevelsPrices = {
|
|||||||
UNLOCKED_0: {
|
UNLOCKED_0: {
|
||||||
id: 0,
|
id: 0,
|
||||||
title: 'UNLOCKED_0',
|
title: 'UNLOCKED_0',
|
||||||
price: parseInt(process.env.UNLOCK_0) || 0
|
price: parseInt(process.env.UNLOCK_0) || 0,
|
||||||
|
description: 'First month - warning',
|
||||||
},
|
},
|
||||||
UNLOCKED_1: {
|
UNLOCKED_1: {
|
||||||
id: 1,
|
id: 1,
|
||||||
title: 'UNLOCKED_1',
|
title: 'UNLOCKED_1',
|
||||||
price: parseInt(process.env.UNLOCK_1) || 10
|
price: parseInt(process.env.UNLOCK_1) || 10,
|
||||||
|
description: 'Second month',
|
||||||
},
|
},
|
||||||
UNLOCKED_2: {
|
UNLOCKED_2: {
|
||||||
id: 2,
|
id: 2,
|
||||||
title: 'UNLOCKED_2',
|
title: 'UNLOCKED_2',
|
||||||
price: parseInt(process.env.UNLOCK_2) || 20
|
price: parseInt(process.env.UNLOCK_2) || 20,
|
||||||
|
description: 'Third month',
|
||||||
},
|
},
|
||||||
UNLOCKED_3: {
|
UNLOCKED_3: {
|
||||||
id: 3,
|
id: 3,
|
||||||
title: 'UNLOCKED_3',
|
title: 'UNLOCKED_3',
|
||||||
price: parseInt(process.env.UNLOCK_3) || 30
|
price: parseInt(process.env.UNLOCK_3) || 30,
|
||||||
|
description: 'Fourth month',
|
||||||
},
|
},
|
||||||
UNLOCKED_4: {
|
UNLOCKED_4: {
|
||||||
id: 4,
|
id: 4,
|
||||||
title: 'UNLOCKED_4',
|
title: 'UNLOCKED_4',
|
||||||
price: parseInt(process.env.UNLOCK_4) || 40
|
price: parseInt(process.env.UNLOCK_4) || 40,
|
||||||
|
description: 'Fifth month',
|
||||||
},
|
},
|
||||||
UNLOCKED_5: {
|
UNLOCKED_5: {
|
||||||
id: 5,
|
id: 5,
|
||||||
title: 'UNLOCKED_5',
|
title: 'UNLOCKED_5',
|
||||||
price: parseInt(process.env.UNLOCK_5) || 50
|
price: parseInt(process.env.UNLOCK_5) || 50,
|
||||||
|
description: 'Sixth month and onward',
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
const csvParserErrors = {
|
const csvParserErrors = {
|
||||||
@@ -50,9 +56,13 @@ const csvParserErrors = {
|
|||||||
};
|
};
|
||||||
const officeRnDAPIErrors = {
|
const officeRnDAPIErrors = {
|
||||||
FAILED_TO_FETCH_MEMBERS: 'Failed to fetch members',
|
FAILED_TO_FETCH_MEMBERS: 'Failed to fetch members',
|
||||||
FAILED_TO_FETCH_BOOKINGS: 'Failed to fetch booking reservations'
|
FAILED_TO_FETCH_BOOKINGS: 'Failed to fetch booking reservations',
|
||||||
|
FAILED_TO_FETCH_FEES: 'Failed to fetch existing fees',
|
||||||
|
FAILED_TO_DELETE_FEES: 'Failed to delete fees in ORD',
|
||||||
|
FAILED_TO_ADD_FEES: 'Failed to add fees in ORD',
|
||||||
};
|
};
|
||||||
const integrationServiceErrors = {
|
const integrationServiceErrors = {
|
||||||
|
IMPORT_SUCCESSFUL_CALCULATION_FAILED: 'Import succeeded but fetching reservations and calculating charges failed',
|
||||||
FAILED_TO_SAVE_BOOKINGS: 'Failed to save booking reservations',
|
FAILED_TO_SAVE_BOOKINGS: 'Failed to save booking reservations',
|
||||||
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',
|
||||||
@@ -71,6 +81,16 @@ const incidentType = {
|
|||||||
BOOKING_CANCELED_LATE: 9,
|
BOOKING_CANCELED_LATE: 9,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const incidentTypeExplanations = {};
|
||||||
|
incidentTypeExplanations[incidentType.UNLOCKED_INCIDENT_RELATED_WITH_RESERVATION] = 'Door left unlocked';
|
||||||
|
incidentTypeExplanations[incidentType.UNLOCKED_INCIDENT_STANDALONE] = 'Door left unlocked';
|
||||||
|
incidentTypeExplanations[incidentType.UNSCHEDULED_INCIDENT_BEFORE_RESERVATION] = 'Room used before reservation started';
|
||||||
|
incidentTypeExplanations[incidentType.UNSCHEDULED_INCIDENT_AFTER_RESERVATION] = 'Room used after reservation started';
|
||||||
|
incidentTypeExplanations[incidentType.UNSCHEDULED_INCIDENT_STANDALONE] = 'Room used without reservation';
|
||||||
|
incidentTypeExplanations[incidentType.BOOKING_MOVED_TO_ANOTHER_DAY] = 'Reservation moved to another day';
|
||||||
|
incidentTypeExplanations[incidentType.BOOKING_SHORTENED] = 'Reservation shortened';
|
||||||
|
incidentTypeExplanations[incidentType.BOOKING_CANCELED_LATE] = 'A reservation canceled after the grace period';
|
||||||
|
|
||||||
const UI_TIMEZONE = process.env.UI_TIMEZONE || 'America/Los_Angeles';
|
const UI_TIMEZONE = process.env.UI_TIMEZONE || 'America/Los_Angeles';
|
||||||
|
|
||||||
const DEFAULT_DATE_FORMAT = 'YYYY-MM-DD';
|
const DEFAULT_DATE_FORMAT = 'YYYY-MM-DD';
|
||||||
@@ -94,6 +114,7 @@ module.exports = {
|
|||||||
unlockedIncidentLevelsPrices,
|
unlockedIncidentLevelsPrices,
|
||||||
integrationServiceErrors,
|
integrationServiceErrors,
|
||||||
incidentType,
|
incidentType,
|
||||||
|
incidentTypeExplanations,
|
||||||
UI_TIMEZONE,
|
UI_TIMEZONE,
|
||||||
DEFAULT_DATE_FORMAT,
|
DEFAULT_DATE_FORMAT,
|
||||||
MAX_BACK_TO_BACK_DIFFERENCE,
|
MAX_BACK_TO_BACK_DIFFERENCE,
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ const { parseDoorLockDataFile, writeDoorLockEvent } = require('../services/doorL
|
|||||||
const { fetchAllBookings, writeBookingReservation } = require('../services/officeRnD/bookings');
|
const { fetchAllBookings, writeBookingReservation } = require('../services/officeRnD/bookings');
|
||||||
const { calculateDoorLockCharges } = require('../services/integration/doorLockCharges');
|
const { calculateDoorLockCharges } = require('../services/integration/doorLockCharges');
|
||||||
const { integrationServiceErrors } = require('../constants/constants');
|
const { integrationServiceErrors } = require('../constants/constants');
|
||||||
|
const { checkBookingChanges } = require('../services/integration/checkBookingChange');
|
||||||
|
|
||||||
const IncomingForm = require('formidable').IncomingForm;
|
const IncomingForm = require('formidable').IncomingForm;
|
||||||
|
|
||||||
@@ -42,11 +43,25 @@ const uploadDoorLockData = (req, res) => {
|
|||||||
|
|
||||||
Promise.all(asyncWriteJobs)
|
Promise.all(asyncWriteJobs)
|
||||||
.then(() => {
|
.then(() => {
|
||||||
res.json({
|
checkBookingChanges()
|
||||||
parsedData,
|
.then(() => {
|
||||||
parserErrors,
|
calculateDoorLockCharges()
|
||||||
unknownMembers
|
.then(() => {
|
||||||
});
|
res.json({
|
||||||
|
parsedData,
|
||||||
|
parserErrors,
|
||||||
|
unknownMembers
|
||||||
|
});
|
||||||
|
})
|
||||||
|
.catch((error) => {
|
||||||
|
console.log('Error : ', error);
|
||||||
|
res.status(500).send(integrationServiceErrors.IMPORT_SUCCESSFUL_CALCULATION_FAILED);
|
||||||
|
});
|
||||||
|
})
|
||||||
|
.catch((error) => {
|
||||||
|
console.log('Error : ', error);
|
||||||
|
res.status(500).send(integrationServiceErrors.IMPORT_SUCCESSFUL_CALCULATION_FAILED);
|
||||||
|
});
|
||||||
})
|
})
|
||||||
.catch((error) => {
|
.catch((error) => {
|
||||||
console.log(integrationServiceErrors.FAILED_TO_SAVE_DOOR_LOCK_ENTRIES);
|
console.log(integrationServiceErrors.FAILED_TO_SAVE_DOOR_LOCK_ENTRIES);
|
||||||
@@ -54,7 +69,7 @@ const uploadDoorLockData = (req, res) => {
|
|||||||
res.status(500).send(integrationServiceErrors.FAILED_TO_SAVE_DATA_GENERIC);
|
res.status(500).send(integrationServiceErrors.FAILED_TO_SAVE_DATA_GENERIC);
|
||||||
});
|
});
|
||||||
|
|
||||||
fetchAllBookings()
|
/*fetchAllBookings()
|
||||||
.then((bookingEntries) => {
|
.then((bookingEntries) => {
|
||||||
const asyncJobs = [];
|
const asyncJobs = [];
|
||||||
bookingEntries.forEach((bookingEntry) => asyncJobs.push(writeBookingReservation(bookingEntry)));
|
bookingEntries.forEach((bookingEntry) => asyncJobs.push(writeBookingReservation(bookingEntry)));
|
||||||
@@ -62,13 +77,18 @@ const uploadDoorLockData = (req, res) => {
|
|||||||
Promise.all(asyncJobs)
|
Promise.all(asyncJobs)
|
||||||
.then(() => {
|
.then(() => {
|
||||||
calculateDoorLockCharges();
|
calculateDoorLockCharges();
|
||||||
});
|
})
|
||||||
|
.catch((error) => {
|
||||||
|
console.log('Error updating booking reservations : ');
|
||||||
|
console.log(error);
|
||||||
|
})
|
||||||
})
|
})
|
||||||
.catch((error) => {
|
.catch((error) => {
|
||||||
console.log(integrationServiceErrors.FAILED_TO_SAVE_BOOKINGS);
|
console.log(integrationServiceErrors.FAILED_TO_SAVE_BOOKINGS);
|
||||||
console.log(error);
|
console.log(error);
|
||||||
return;
|
return;
|
||||||
});
|
});
|
||||||
|
*/
|
||||||
})
|
})
|
||||||
.catch((error) => {
|
.catch((error) => {
|
||||||
res.status(500).send(error);
|
res.status(500).send(error);
|
||||||
|
|||||||
@@ -2,6 +2,9 @@
|
|||||||
|
|
||||||
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 } = require('../services/integration/reports');
|
||||||
|
const { getMembersFeesForDateRange } = require('../services/integration/invoiceIntegration');
|
||||||
|
const { deleteFeesFromORD, addFeesToORD } = require('../services/officeRnD/fees');
|
||||||
|
const { checkBookingChanges } = require('../services/integration/checkBookingChange');
|
||||||
|
|
||||||
const getKnownOfficeResourceMappings = (req, res) => {
|
const getKnownOfficeResourceMappings = (req, res) => {
|
||||||
const dataToFetch = [getMappingsFromDatabase(), fetchOffices(), fetchResources() ];
|
const dataToFetch = [getMappingsFromDatabase(), fetchOffices(), fetchResources() ];
|
||||||
@@ -38,7 +41,7 @@ const getAllIncidentsController = (req, res) => {
|
|||||||
endDate: req.params.endDate,
|
endDate: req.params.endDate,
|
||||||
};
|
};
|
||||||
|
|
||||||
getAllIncidents(dateRange)
|
getAllIncidents(dateRange, [])
|
||||||
.then((incidents) => {
|
.then((incidents) => {
|
||||||
res.send(incidents);
|
res.send(incidents);
|
||||||
})
|
})
|
||||||
@@ -55,7 +58,7 @@ const getMemberIncidents = (req, res) => {
|
|||||||
endDate: req.params.endDate,
|
endDate: req.params.endDate,
|
||||||
};
|
};
|
||||||
|
|
||||||
getAllIncidents(dateRange, memberId)
|
getAllIncidents(dateRange, [memberId])
|
||||||
.then((incidents) => {
|
.then((incidents) => {
|
||||||
res.send(incidents);
|
res.send(incidents);
|
||||||
})
|
})
|
||||||
@@ -65,9 +68,53 @@ const getMemberIncidents = (req, res) => {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const addFees = (req, res) => {
|
||||||
|
const memberIds = req.body && req.body.memberIds ? req.body.memberIds : [];
|
||||||
|
const dateRange = req.body && req.body.dateRange ? req.body.dateRange : {startDate: null, endDate: null};
|
||||||
|
const { startDate, endDate } = dateRange;
|
||||||
|
|
||||||
|
if (startDate && endDate && Array.isArray(memberIds)){
|
||||||
|
checkBookingChanges()
|
||||||
|
.then(() => {
|
||||||
|
deleteFeesFromORD(dateRange, memberIds)
|
||||||
|
.then(() => {
|
||||||
|
// TODO: Change this to parallel execution
|
||||||
|
getMembersFeesForDateRange(dateRange, memberIds)
|
||||||
|
.then((allFees) => {
|
||||||
|
addFeesToORD(allFees)
|
||||||
|
.then((numberOfInsertedFees) => {
|
||||||
|
res.send({
|
||||||
|
status: 'ok',
|
||||||
|
numberOfInsertedFees
|
||||||
|
});
|
||||||
|
})
|
||||||
|
.catch((error) => {
|
||||||
|
console.log('Error adding fees');
|
||||||
|
res.status(500).send(error);
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.catch((error) => {
|
||||||
|
console.log(error);
|
||||||
|
res.status(500).send(error);
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.catch((error) => {
|
||||||
|
res.status(500).send(error);
|
||||||
|
});
|
||||||
|
})
|
||||||
|
.catch((error) => {
|
||||||
|
console.log('Error with updating booking reservations');
|
||||||
|
res.status(500).send(error);
|
||||||
|
})
|
||||||
|
}else{
|
||||||
|
res.status(400).send('Date range is missing');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
getKnownOfficeResourceMappings,
|
getKnownOfficeResourceMappings,
|
||||||
addNewMapping,
|
addNewMapping,
|
||||||
getAllIncidentsController,
|
getAllIncidentsController,
|
||||||
getMemberIncidents,
|
getMemberIncidents,
|
||||||
|
addFees,
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,41 +1,6 @@
|
|||||||
'use strict';
|
'use strict';
|
||||||
require('dotenv').config();
|
require('dotenv').config();
|
||||||
|
|
||||||
const { fetchAllBookings, bulkWriteReservationsWithChangesTracking } = require('../services/officeRnD/bookings');
|
const { checkBookingChanges } = require('../services/integration/checkBookingChange');
|
||||||
|
|
||||||
const { chargeBookingChanges } = require('../services/integration/bookingChangeCharges');
|
checkBookingChanges().then(() => process.exit()).catch(() => process.exit());
|
||||||
const { bulkWriteChanges } = require('../services/integration/bookingChangeLog');
|
|
||||||
|
|
||||||
const checkBookingChanges = () => {
|
|
||||||
fetchAllBookings()
|
|
||||||
.then((reservations) => {
|
|
||||||
bulkWriteReservationsWithChangesTracking(reservations)
|
|
||||||
.then((changes) => {
|
|
||||||
bulkWriteChanges(changes)
|
|
||||||
.then(() => {
|
|
||||||
chargeBookingChanges(changes)
|
|
||||||
.then(() => {
|
|
||||||
process.exit();
|
|
||||||
})
|
|
||||||
.catch((error) => {
|
|
||||||
console.log('Error creating charges ', error);
|
|
||||||
process.exit();
|
|
||||||
});
|
|
||||||
})
|
|
||||||
.catch((error) => {
|
|
||||||
console.log('Error bulk write booking reservation change log :', error);
|
|
||||||
process.exit();
|
|
||||||
});
|
|
||||||
})
|
|
||||||
.catch((error) => {
|
|
||||||
console.log('Error bulk write booking reservations :', error);
|
|
||||||
process.exit();
|
|
||||||
});
|
|
||||||
})
|
|
||||||
.catch((error) => {
|
|
||||||
console.log('Error fetching bookings from ORD ', error);
|
|
||||||
process.exit();
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
checkBookingChanges();
|
|
||||||
|
|||||||
@@ -24,3 +24,8 @@ BOOKING_CHANGE_PERCENTAGE_CHARGE=Percentage of hourly reate to apply for cancell
|
|||||||
ALLOWED_BOOKING_CANCELLATION_TIME=Time from creation (in minutes) in which cancellation is not charged
|
ALLOWED_BOOKING_CANCELLATION_TIME=Time from creation (in minutes) in which cancellation is not charged
|
||||||
|
|
||||||
SEQUELIZE_LOGGING=0 - false, 1 - true (console logging)
|
SEQUELIZE_LOGGING=0 - false, 1 - true (console logging)
|
||||||
|
|
||||||
|
#More about pool option : http://docs.sequelizejs.com/class/lib/sequelize.js~Sequelize.html
|
||||||
|
DB_POOL_MAX_CONNECTIONS=Maximum number of connection in pool (ex. 18)
|
||||||
|
DB_POOL_ACQUIRE=The maximum time, in milliseconds, that pool will try to get connection before throwing error (ex. 120000)
|
||||||
|
DB_POOL_EVICT=The time interval, in milliseconds, after which sequelize-pool will remove idle connections. (ex. 10000)
|
||||||
|
|||||||
@@ -2,8 +2,14 @@
|
|||||||
|
|
||||||
const { apiStatusCheck } = require('../controllers/apiStatusCheck');
|
const { apiStatusCheck } = require('../controllers/apiStatusCheck');
|
||||||
const { uploadDoorLockData } = require('../controllers/doorLock');
|
const { uploadDoorLockData } = require('../controllers/doorLock');
|
||||||
const { getKnownOfficeResourceMappings, addNewMapping, getAllIncidentsController, getMemberIncidents } = require('../controllers/integration');
|
|
||||||
const { fetchMembersList } = require('../controllers/officeRnD');
|
const { fetchMembersList } = require('../controllers/officeRnD');
|
||||||
|
const {
|
||||||
|
getKnownOfficeResourceMappings,
|
||||||
|
addNewMapping,
|
||||||
|
getAllIncidentsController,
|
||||||
|
getMemberIncidents,
|
||||||
|
addFees
|
||||||
|
} = require('../controllers/integration');
|
||||||
|
|
||||||
const { calculateDoorLockCharges } = require('../services/integration/doorLockCharges');
|
const { calculateDoorLockCharges } = require('../services/integration/doorLockCharges');
|
||||||
|
|
||||||
@@ -21,6 +27,8 @@ router.get('/integration/report/allIncidents/:startDate/:endDate', getAllInciden
|
|||||||
|
|
||||||
router.get('/officeRnD/membersList', fetchMembersList);
|
router.get('/officeRnD/membersList', fetchMembersList);
|
||||||
|
|
||||||
|
router.post('/integration/addFees', addFees);
|
||||||
|
|
||||||
// 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();});
|
||||||
|
|
||||||
|
|||||||
@@ -1,150 +0,0 @@
|
|||||||
'use strict';
|
|
||||||
|
|
||||||
const db = require('../models/index');
|
|
||||||
const fs = require('fs');
|
|
||||||
const csv = require('csv-parser');
|
|
||||||
const moment = require('moment');
|
|
||||||
|
|
||||||
const {
|
|
||||||
USER_ENTRY_EVENT,
|
|
||||||
ENABLE_PASSAGE_MODE,
|
|
||||||
DISABLE_PASSAGE_MODE,
|
|
||||||
USER_UNLOCKED_DOOR,
|
|
||||||
USER_LOCKED_DOOR,
|
|
||||||
VALID_CSV_HEADERS,
|
|
||||||
csvParserErrors,
|
|
||||||
} = require('../constants/constants');
|
|
||||||
|
|
||||||
const { fetchAllMembers, findMember } = require('../services/officeRnD/members');
|
|
||||||
|
|
||||||
|
|
||||||
const parseDoorLockDataFile = (file) => {
|
|
||||||
return new Promise ((resolve, reject) => {
|
|
||||||
const results = [];
|
|
||||||
const errors = [];
|
|
||||||
const unknownMembers = [];
|
|
||||||
let isValidFile = true;
|
|
||||||
|
|
||||||
fetchAllMembers()
|
|
||||||
.then(() => {
|
|
||||||
fs.createReadStream(file.path)
|
|
||||||
.pipe(csv({
|
|
||||||
mapHeaders: ({ header, index }) => header.trim().toLowerCase(),
|
|
||||||
mapValues: ({ header, index, value }) => value.trim()
|
|
||||||
}))
|
|
||||||
.on('headers', (headers) => {
|
|
||||||
|
|
||||||
const sortedValidHeadersArray = VALID_CSV_HEADERS.concat().sort();
|
|
||||||
const sortedParsedHeadersArray = headers.map(header => header.trim()).sort();
|
|
||||||
|
|
||||||
let validHeaders = true;
|
|
||||||
if (sortedParsedHeadersArray.length !== sortedValidHeadersArray.length) {
|
|
||||||
validHeaders = false;
|
|
||||||
}else {
|
|
||||||
for (let i = 0; i < sortedValidHeadersArray.length; i++){
|
|
||||||
validHeaders = validHeaders
|
|
||||||
&& (sortedValidHeadersArray[i] === sortedParsedHeadersArray[i]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!validHeaders){
|
|
||||||
isValidFile = false;
|
|
||||||
errors.push({
|
|
||||||
error: csvParserErrors.INVALID_HEADERS,
|
|
||||||
details: `Expected headers : ${JSON.stringify(VALID_CSV_HEADERS)}`,
|
|
||||||
file: file.name,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.on('data', (data) => {
|
|
||||||
if (!isValidFile) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const eventType = data.event.trim();
|
|
||||||
if ([USER_ENTRY_EVENT, ENABLE_PASSAGE_MODE, DISABLE_PASSAGE_MODE].includes(eventType)){
|
|
||||||
results.push(data);
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.on('end', () => {
|
|
||||||
const parsedData = [];
|
|
||||||
let i = 0;
|
|
||||||
while (i < results.length){
|
|
||||||
//Verify pair
|
|
||||||
//First entry type should be user entry and second should be enable / disable passage
|
|
||||||
const firstEntry = results[i];
|
|
||||||
const secondEntry = results[i+1];
|
|
||||||
|
|
||||||
if (firstEntry && (firstEntry.event === USER_ENTRY_EVENT)){
|
|
||||||
const memberObject = findMember(firstEntry.name);
|
|
||||||
|
|
||||||
if (!memberObject){
|
|
||||||
//Check if member is already labeled as unknown
|
|
||||||
const unknownMember = unknownMembers.find((member) => member.details === firstEntry.name);
|
|
||||||
if (!unknownMember){
|
|
||||||
unknownMembers.push({
|
|
||||||
error: csvParserErrors.UNKNOWN_MEMBER,
|
|
||||||
details: firstEntry.name,
|
|
||||||
file: file.name,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (secondEntry && (secondEntry.event === ENABLE_PASSAGE_MODE || secondEntry.event === DISABLE_PASSAGE_MODE)){
|
|
||||||
const event = (secondEntry.event === ENABLE_PASSAGE_MODE) ? USER_UNLOCKED_DOOR : USER_LOCKED_DOOR;
|
|
||||||
const dateTimeString = `${firstEntry.date} ${firstEntry.time}`;
|
|
||||||
const timestamp = moment.utc(dateTimeString, 'MM/DD/YY HH:mm:ss A').toISOString();
|
|
||||||
|
|
||||||
//Verify that member is registered in OfficeRnD system
|
|
||||||
if (memberObject){
|
|
||||||
const entryData = {
|
|
||||||
memberName: firstEntry.name,
|
|
||||||
memberNumber: firstEntry['user no'],
|
|
||||||
memberId: memberObject.memberId,
|
|
||||||
timestamp,
|
|
||||||
event,
|
|
||||||
};
|
|
||||||
|
|
||||||
parsedData.push(entryData);
|
|
||||||
}
|
|
||||||
i+=2;
|
|
||||||
} else {
|
|
||||||
errors.push({
|
|
||||||
error: csvParserErrors.INVALID_ENTRY_EXPECTED_PASSAGE_MODE,
|
|
||||||
details: firstEntry,
|
|
||||||
file: file.name,
|
|
||||||
});
|
|
||||||
i+=1;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
errors.push({
|
|
||||||
error: csvParserErrors.INVALID_ENTRY_EXPECTED_USER,
|
|
||||||
details: firstEntry,
|
|
||||||
file: file.name,
|
|
||||||
});
|
|
||||||
i+=1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
resolve({
|
|
||||||
parsedData,
|
|
||||||
unknownMembers,
|
|
||||||
errors
|
|
||||||
});
|
|
||||||
});
|
|
||||||
})
|
|
||||||
.catch((error) => {
|
|
||||||
reject(error);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
const writeDoorLockEvent = (entry) => {
|
|
||||||
db.doorLockEvent.findOrCreate({where: {...entry}, defaults: {...entry}})
|
|
||||||
.then()
|
|
||||||
.catch();
|
|
||||||
};
|
|
||||||
|
|
||||||
module.exports = {
|
|
||||||
parseDoorLockDataFile,
|
|
||||||
writeDoorLockEvent,
|
|
||||||
};
|
|
||||||
@@ -97,7 +97,7 @@ const chargeBookingChanges = (changes) => {
|
|||||||
incidents.push(incident);
|
incidents.push(incident);
|
||||||
}
|
}
|
||||||
}else{
|
}else{
|
||||||
const differenceFromCreation = reservationCreationTimestamp.diff(moment.utc(), 'minutes');
|
const differenceFromCreation = moment.utc().diff(reservationCreationTimestamp, 'minutes');
|
||||||
|
|
||||||
if (differenceFromCreation > ALLOWED_BOOKING_CANCELLATION_TIME){
|
if (differenceFromCreation > ALLOWED_BOOKING_CANCELLATION_TIME){
|
||||||
const chargeFee = 2 * reservationHourlyRate * oldReservationLength * BOOKING_CHANGE_PERCENTAGE_CHARGE / 100;
|
const chargeFee = 2 * reservationHourlyRate * oldReservationLength * BOOKING_CHANGE_PERCENTAGE_CHARGE / 100;
|
||||||
|
|||||||
53
services/integration/bookings.js
Normal file
53
services/integration/bookings.js
Normal file
@@ -0,0 +1,53 @@
|
|||||||
|
'use strict';
|
||||||
|
|
||||||
|
const Op = require('sequelize').Op;
|
||||||
|
const db = require('../../models/index');
|
||||||
|
const moment = require('moment-timezone');
|
||||||
|
|
||||||
|
const { DEFAULT_DATE_FORMAT, UI_TIMEZONE } = require('../../constants/constants');
|
||||||
|
|
||||||
|
const getActiveBookingsForMembersInDateRange = (dateRange, memberIds) => {
|
||||||
|
const startDate = moment.tz(dateRange.startDate, DEFAULT_DATE_FORMAT, UI_TIMEZONE).startOf('day');
|
||||||
|
const endDate = moment.tz(dateRange.endDate, DEFAULT_DATE_FORMAT, UI_TIMEZONE).endOf('day');
|
||||||
|
|
||||||
|
const attributes = [
|
||||||
|
'id',
|
||||||
|
'reservationId',
|
||||||
|
'memberId',
|
||||||
|
'officeId',
|
||||||
|
'resourceId',
|
||||||
|
'start',
|
||||||
|
'end',
|
||||||
|
'timezone',
|
||||||
|
'canceled',
|
||||||
|
'hourlyRate'
|
||||||
|
];
|
||||||
|
|
||||||
|
const filters = {
|
||||||
|
canceled: false,
|
||||||
|
};
|
||||||
|
|
||||||
|
if (startDate && endDate) {
|
||||||
|
filters.start = {
|
||||||
|
[Op.gte]: startDate.toISOString()
|
||||||
|
};
|
||||||
|
filters.end = {
|
||||||
|
[Op.lte]: endDate.toISOString(),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
if (memberIds.length > 0){
|
||||||
|
filters.memberId = {
|
||||||
|
[Op.in]: memberIds
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return db.bookingReservation.findAll({
|
||||||
|
attributes,
|
||||||
|
where: filters,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
getActiveBookingsForMembersInDateRange,
|
||||||
|
};
|
||||||
44
services/integration/checkBookingChange.js
Normal file
44
services/integration/checkBookingChange.js
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
'use strict';
|
||||||
|
|
||||||
|
const { fetchAllBookings, bulkWriteReservationsWithChangesTracking } = require('../officeRnD/bookings');
|
||||||
|
|
||||||
|
const { chargeBookingChanges } = require('./bookingChangeCharges');
|
||||||
|
const { bulkWriteChanges } = require('./bookingChangeLog');
|
||||||
|
|
||||||
|
const checkBookingChanges = () => {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
fetchAllBookings()
|
||||||
|
.then((reservations) => {
|
||||||
|
bulkWriteReservationsWithChangesTracking(reservations)
|
||||||
|
.then((changes) => {
|
||||||
|
bulkWriteChanges(changes)
|
||||||
|
.then(() => {
|
||||||
|
chargeBookingChanges(changes)
|
||||||
|
.then(() => {
|
||||||
|
resolve(true);
|
||||||
|
})
|
||||||
|
.catch((error) => {
|
||||||
|
console.log('Error creating charges ', error);
|
||||||
|
reject(error);
|
||||||
|
});
|
||||||
|
})
|
||||||
|
.catch((error) => {
|
||||||
|
console.log('Error bulk write booking reservation change log :', error);
|
||||||
|
reject(error);
|
||||||
|
});
|
||||||
|
})
|
||||||
|
.catch((error) => {
|
||||||
|
console.log('Error bulk write booking reservations :', error);
|
||||||
|
reject(error);
|
||||||
|
});
|
||||||
|
})
|
||||||
|
.catch((error) => {
|
||||||
|
console.log('Error fetching bookings from ORD ', error);
|
||||||
|
reject(error);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
checkBookingChanges
|
||||||
|
};
|
||||||
@@ -510,52 +510,66 @@ const getIncidentData = (reservation) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const calculateDoorLockCharges = () => {
|
const calculateDoorLockCharges = () => {
|
||||||
getAllFinishedBookings()
|
return new Promise((resolve, reject) => {
|
||||||
.then((reservations) => {
|
getAllFinishedBookings()
|
||||||
const unlockedIncidents = [];
|
.then((reservations) => {
|
||||||
const unscheduledIncidents = [];
|
const unlockedIncidents = [];
|
||||||
|
const unscheduledIncidents = [];
|
||||||
|
|
||||||
const asyncCheckForIncidents = [];
|
const asyncCheckForIncidents = [];
|
||||||
|
|
||||||
reservations.forEach((reservation) => {
|
reservations.forEach((reservation) => {
|
||||||
asyncCheckForIncidents.push(getIncidentData(reservation));
|
asyncCheckForIncidents.push(getIncidentData(reservation));
|
||||||
});
|
});
|
||||||
|
|
||||||
Promise.all(asyncCheckForIncidents)
|
Promise.all(asyncCheckForIncidents)
|
||||||
.then((allReservationsIncidents) => {
|
.then((allReservationsIncidents) => {
|
||||||
allReservationsIncidents.forEach((reservationIncidents) => {
|
allReservationsIncidents.forEach((reservationIncidents) => {
|
||||||
reservationIncidents.forEach((incident) => {
|
reservationIncidents.forEach((incident) => {
|
||||||
if (incident.error) {
|
if (incident.error) {
|
||||||
console.log('Error checking incident : ', incident.error);
|
console.log('Error checking incident : ', incident.error);
|
||||||
} else if (incident.incidentType) {
|
} else if (incident.incidentType) {
|
||||||
switch (incident.incidentType) {
|
switch (incident.incidentType) {
|
||||||
case incidentType.UNLOCKED_INCIDENT_RELATED_WITH_RESERVATION:
|
case incidentType.UNLOCKED_INCIDENT_RELATED_WITH_RESERVATION:
|
||||||
case incidentType.UNLOCKED_INCIDENT_STANDALONE:
|
case incidentType.UNLOCKED_INCIDENT_STANDALONE:
|
||||||
unlockedIncidents.push(incident);
|
unlockedIncidents.push(incident);
|
||||||
break;
|
break;
|
||||||
case incidentType.UNSCHEDULED_INCIDENT_BEFORE_RESERVATION:
|
case incidentType.UNSCHEDULED_INCIDENT_BEFORE_RESERVATION:
|
||||||
case incidentType.UNSCHEDULED_INCIDENT_AFTER_RESERVATION:
|
case incidentType.UNSCHEDULED_INCIDENT_AFTER_RESERVATION:
|
||||||
case incidentType.UNSCHEDULED_INCIDENT_STANDALONE:
|
case incidentType.UNSCHEDULED_INCIDENT_STANDALONE:
|
||||||
unscheduledIncidents.push(incident);
|
unscheduledIncidents.push(incident);
|
||||||
break;
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
});
|
||||||
});
|
});
|
||||||
});
|
|
||||||
|
|
||||||
insertUnscheduledIncidents(unscheduledIncidents)
|
setUnlockedIncidentsLevel(unlockedIncidents)
|
||||||
.catch((error) => console.log(error));
|
.then((completedUnlockedIncidents) => {
|
||||||
|
const insertIncidentsJobs = [insertUnscheduledIncidents(unscheduledIncidents), insertUnlockedIncidents(completedUnlockedIncidents)];
|
||||||
|
|
||||||
setUnlockedIncidentsLevel(unlockedIncidents)
|
Promise.all(insertIncidentsJobs)
|
||||||
.then((completedUnlockedIncidents) => {
|
.then(() => {
|
||||||
insertUnlockedIncidents(completedUnlockedIncidents)
|
resolve(true);
|
||||||
.catch((error) => console.log(error));
|
})
|
||||||
})
|
.catch((error) => {
|
||||||
.catch((error) => console.log(error));
|
reject(error);
|
||||||
})
|
});
|
||||||
.catch((error) => console.log(error));
|
|
||||||
})
|
/*
|
||||||
.catch((error) => console.log(error));
|
insertUnscheduledIncidents(unscheduledIncidents)
|
||||||
|
.catch((error) => console.log(error));
|
||||||
|
|
||||||
|
insertUnlockedIncidents(completedUnlockedIncidents)
|
||||||
|
.catch((error) => console.log(error));
|
||||||
|
*/
|
||||||
|
})
|
||||||
|
.catch((error) => reject(error));
|
||||||
|
})
|
||||||
|
.catch((error) => reject(error));
|
||||||
|
})
|
||||||
|
.catch((error) => reject(error));
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
|
|||||||
257
services/integration/invoiceIntegration.js
Normal file
257
services/integration/invoiceIntegration.js
Normal file
@@ -0,0 +1,257 @@
|
|||||||
|
'use strict';
|
||||||
|
|
||||||
|
const moment = require('moment-timezone');
|
||||||
|
|
||||||
|
const { getAllIncidents } = require('./reports');
|
||||||
|
const { getActiveBookingsForMembersInDateRange } = require('./bookings');
|
||||||
|
|
||||||
|
const { DEFAULT_DATE_FORMAT, UI_TIMEZONE, incidentTypeExplanations, incidentType, unlockedIncidentLevelsPrices } = require('../../constants/constants');
|
||||||
|
const { getResourceMappings } = require('../officeRnD/resources');
|
||||||
|
const { fetchAllMembers } = require('../officeRnD/members');
|
||||||
|
|
||||||
|
const createFeeFromIncident = (incident) => {
|
||||||
|
const {
|
||||||
|
memberId,
|
||||||
|
officeId,
|
||||||
|
officeName,
|
||||||
|
resourceName,
|
||||||
|
oldResourceName,
|
||||||
|
newResourceName,
|
||||||
|
bookingStartRaw,
|
||||||
|
bookingEndRaw,
|
||||||
|
oldBookingStartRaw,
|
||||||
|
oldBookingEndRaw,
|
||||||
|
newBookingStartRaw,
|
||||||
|
newBookingEndRaw,
|
||||||
|
unlockTimestampRaw,
|
||||||
|
lockTimestampRaw,
|
||||||
|
incidentLevel,
|
||||||
|
timeIntervalsToCharge,
|
||||||
|
incidentPrice,
|
||||||
|
chargePrice,
|
||||||
|
totalChargeFee,
|
||||||
|
incidentTimestampRaw
|
||||||
|
} = incident;
|
||||||
|
const incidentTypeNumber = incident.incidentType;
|
||||||
|
|
||||||
|
const incidentExplanation = incidentTypeExplanations[incidentTypeNumber];
|
||||||
|
|
||||||
|
let date = '';
|
||||||
|
let price = 0;
|
||||||
|
let quantity = 0;
|
||||||
|
let priceExplanation = '';
|
||||||
|
let bookingTimeExplanation = '';
|
||||||
|
let incidentTimeExplanation = '';
|
||||||
|
let additionalIncidentExplanation = '';
|
||||||
|
|
||||||
|
let roomExplanation = '';
|
||||||
|
let dateExplanation = '';
|
||||||
|
|
||||||
|
const bookingStartMoment = moment.tz(bookingStartRaw, UI_TIMEZONE);
|
||||||
|
const bookingEndMoment = moment.tz(bookingEndRaw, UI_TIMEZONE);
|
||||||
|
const unlockMoment = moment.tz(unlockTimestampRaw, UI_TIMEZONE);
|
||||||
|
const lockMoment = moment.tz(lockTimestampRaw, UI_TIMEZONE);
|
||||||
|
|
||||||
|
const oldBookingStartMoment = moment.tz(oldBookingStartRaw, UI_TIMEZONE);
|
||||||
|
const oldBookingEndMoment = moment.tz(oldBookingEndRaw, UI_TIMEZONE);
|
||||||
|
const newBookingStartMoment = moment.tz(newBookingStartRaw, UI_TIMEZONE);
|
||||||
|
const newBookingEndMoment = moment.tz(newBookingEndRaw, UI_TIMEZONE);
|
||||||
|
|
||||||
|
const incidentTimestampMoment = moment.tz(incidentTimestampRaw, UI_TIMEZONE);
|
||||||
|
|
||||||
|
switch (incidentTypeNumber){
|
||||||
|
case incidentType.UNLOCKED_INCIDENT_RELATED_WITH_RESERVATION:
|
||||||
|
roomExplanation = resourceName;
|
||||||
|
dateExplanation = bookingStartMoment.clone().startOf('day').format('MMM DD, YYYY');
|
||||||
|
bookingTimeExplanation = `${bookingStartMoment.clone().format('HH:mm a')} - ${bookingEndMoment.clone().format('HH:mm a')}`;
|
||||||
|
incidentTimeExplanation = `UNLOCK : ${unlockMoment.clone().format('HH:mm a')}`;
|
||||||
|
unlockedIncidentLevelsPrices[incidentLevel].description
|
||||||
|
additionalIncidentExplanation = unlockedIncidentLevelsPrices[incidentLevel].description;
|
||||||
|
|
||||||
|
date = bookingStartMoment.clone().startOf('day').format();
|
||||||
|
|
||||||
|
price = incidentPrice;
|
||||||
|
quantity = 1;
|
||||||
|
priceExplanation = `$${price.toFixed(2)}, 1 x $${price.toFixed(2)}`;
|
||||||
|
break;
|
||||||
|
case incidentType.UNSCHEDULED_INCIDENT_BEFORE_RESERVATION:
|
||||||
|
roomExplanation = resourceName;
|
||||||
|
dateExplanation = bookingStartMoment.clone().startOf('day').format('MMM DD, YYYY');
|
||||||
|
bookingTimeExplanation = `${bookingStartMoment.clone().format('HH:mm a')} - ${bookingEndMoment.clone().format('HH:mm a')}`;
|
||||||
|
incidentTimeExplanation = `UNLOCK : ${unlockMoment.clone().format('HH:mm a')}`;
|
||||||
|
|
||||||
|
date = bookingStartMoment.clone().startOf('day').format();
|
||||||
|
|
||||||
|
price = chargePrice;
|
||||||
|
quantity = timeIntervalsToCharge;
|
||||||
|
priceExplanation = `$${totalChargeFee.toFixed(2)}, ${quantity} x $${price.toFixed(2)}`;
|
||||||
|
break;
|
||||||
|
case incidentType.UNSCHEDULED_INCIDENT_AFTER_RESERVATION:
|
||||||
|
roomExplanation = resourceName;
|
||||||
|
dateExplanation = bookingStartMoment.clone().startOf('day').format('MMM DD, YYYY');
|
||||||
|
bookingTimeExplanation = `${bookingStartMoment.clone().format('HH:mm a')} - ${bookingEndMoment.clone().format('HH:mm a')}`;
|
||||||
|
incidentTimeExplanation = `LOCK : ${lockMoment.clone().format('HH:mm a')}`;
|
||||||
|
|
||||||
|
date = bookingStartMoment.clone().startOf('day').format();
|
||||||
|
|
||||||
|
price = chargePrice;
|
||||||
|
quantity = timeIntervalsToCharge;
|
||||||
|
priceExplanation = `$${totalChargeFee.toFixed(2)}, ${quantity} x $${price.toFixed(2)}`;
|
||||||
|
break;
|
||||||
|
case incidentType.UNLOCKED_INCIDENT_STANDALONE:
|
||||||
|
roomExplanation = resourceName;
|
||||||
|
dateExplanation = unlockMoment.clone().startOf('day').format('MMM DD, YYYY');
|
||||||
|
bookingTimeExplanation = `NO RESERVATION`;
|
||||||
|
incidentTimeExplanation = `UNLOCK : ${unlockMoment.clone().format('HH:mm a')}`;
|
||||||
|
additionalIncidentExplanation = unlockedIncidentLevelsPrices[incidentLevel].description;
|
||||||
|
|
||||||
|
date = unlockMoment.clone().startOf('day').format();
|
||||||
|
|
||||||
|
price = incidentPrice;
|
||||||
|
quantity = 1;
|
||||||
|
priceExplanation = `$${price.toFixed(2)}, 1 x $${price.toFixed(2)}`;
|
||||||
|
break;
|
||||||
|
case incidentType.UNSCHEDULED_INCIDENT_STANDALONE:
|
||||||
|
roomExplanation = resourceName;
|
||||||
|
dateExplanation = unlockMoment.clone().startOf('day').format('MMM DD, YYYY');
|
||||||
|
bookingTimeExplanation = `NO RESERVATION`;
|
||||||
|
incidentTimeExplanation = `UNLOCK : ${unlockMoment.clone().format('HH:mm a')} LOCK : ${lockMoment.clone().format('HH:mm a')}`;
|
||||||
|
additionalIncidentExplanation = '';
|
||||||
|
|
||||||
|
date = unlockMoment.clone().startOf('day').format();
|
||||||
|
|
||||||
|
price = chargePrice;
|
||||||
|
quantity = timeIntervalsToCharge;
|
||||||
|
priceExplanation = `$${totalChargeFee.toFixed(2)}, ${quantity} x $${price.toFixed(2)}`;
|
||||||
|
break;
|
||||||
|
case incidentType.BOOKING_MOVED_TO_ANOTHER_DAY:
|
||||||
|
if (oldResourceName !== newResourceName){
|
||||||
|
roomExplanation = `${oldResourceName} -> ${newResourceName}`;
|
||||||
|
}else{
|
||||||
|
roomExplanation = oldResourceName;
|
||||||
|
}
|
||||||
|
|
||||||
|
dateExplanation = `${oldBookingStartMoment.clone().format('MMM DD, YYYY')} -> ${newBookingStartMoment.clone().format('MMM DD, YYYY')}`;
|
||||||
|
bookingTimeExplanation = `(${oldBookingStartMoment.clone().format('HH:mm a')} - ${oldBookingEndMoment.clone().format('HH:mm a')}) -> (${newBookingStartMoment.clone().format('HH:mm a')} - ${newBookingEndMoment.clone().format('HH:mm a')})`;
|
||||||
|
incidentTimeExplanation = `MOVED ON : ${incidentTimestampMoment.clone().format('MMM DD, YYYY')}`;
|
||||||
|
|
||||||
|
date = incidentTimestampMoment.clone().startOf('day').format();
|
||||||
|
|
||||||
|
price = totalChargeFee;
|
||||||
|
quantity = 1;
|
||||||
|
priceExplanation = `$${totalChargeFee.toFixed(2)}, 1 x $${price.toFixed(2)}`;
|
||||||
|
break;
|
||||||
|
case incidentType.BOOKING_SHORTENED:
|
||||||
|
if (oldResourceName !== newResourceName){
|
||||||
|
roomExplanation = `${oldResourceName} -> ${newResourceName}`;
|
||||||
|
}else{
|
||||||
|
roomExplanation = oldResourceName;
|
||||||
|
}
|
||||||
|
|
||||||
|
dateExplanation = `${oldBookingStartMoment.clone().format('MMM DD, YYYY')}`;
|
||||||
|
bookingTimeExplanation = `(${oldBookingStartMoment.clone().format('HH:mm a')} - ${oldBookingEndMoment.clone().format('HH:mm a')}) -> (${newBookingStartMoment.clone().format('HH:mm a')} - ${newBookingEndMoment.clone().format('HH:mm a')})`;
|
||||||
|
incidentTimeExplanation = `SHORTENED ON : ${incidentTimestampMoment.clone().format('MMM DD, YYYY')}`;
|
||||||
|
|
||||||
|
date = incidentTimestampMoment.clone().startOf('day').format();
|
||||||
|
|
||||||
|
price = totalChargeFee;
|
||||||
|
quantity = 1;
|
||||||
|
priceExplanation = `$${totalChargeFee.toFixed(2)}, 1 x $${price.toFixed(2)}`;
|
||||||
|
break;
|
||||||
|
case incidentType.BOOKING_CANCELED_LATE:
|
||||||
|
roomExplanation = oldResourceName;
|
||||||
|
dateExplanation = `${oldBookingStartMoment.clone().format('MMM DD, YYYY')}`;
|
||||||
|
bookingTimeExplanation = `${oldBookingStartMoment.clone().format('HH:mm a')} - ${oldBookingEndMoment.clone().format('HH:mm a')}`;
|
||||||
|
incidentTimeExplanation = `CANCELED ON : ${incidentTimestampMoment.clone().format('MMM DD, YYYY')}`;
|
||||||
|
|
||||||
|
date = incidentTimestampMoment.clone().startOf('day').format();
|
||||||
|
|
||||||
|
price = totalChargeFee;
|
||||||
|
quantity = 1;
|
||||||
|
priceExplanation = `$${totalChargeFee.toFixed(2)}, 1 x $${price.toFixed(2)}`;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
const formattedName = `[INCIDENT FEES] ${officeName}, ${roomExplanation}, ${dateExplanation}, ${bookingTimeExplanation}, ${incidentTimeExplanation}, ${incidentExplanation}, ${additionalIncidentExplanation} ${additionalIncidentExplanation !== '' ? ',' : ''} ${priceExplanation}`;
|
||||||
|
|
||||||
|
return {
|
||||||
|
name: formattedName,
|
||||||
|
price,
|
||||||
|
quantity,
|
||||||
|
date,
|
||||||
|
member: memberId,
|
||||||
|
team: null,
|
||||||
|
office: officeId,
|
||||||
|
isPersonal: false,
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const createFeeFromBooking = (booking, resourceMappings) => {
|
||||||
|
const { officeId, resourceId, memberId, start, end, timezone, hourlyRate } = booking;
|
||||||
|
const { officesMap, resourcesMap } = resourceMappings;
|
||||||
|
|
||||||
|
const startMoment = moment.tz(start, DEFAULT_DATE_FORMAT, timezone);
|
||||||
|
const endMoment = moment.tz(end, DEFAULT_DATE_FORMAT, timezone);
|
||||||
|
const reservationLength = endMoment.diff(startMoment, 'hours', true);
|
||||||
|
|
||||||
|
const officeName = officesMap[officeId].officeName || 'Unknown';
|
||||||
|
const resourceName = resourcesMap[resourceId].resourceName || 'Unknown';
|
||||||
|
|
||||||
|
const totalCost = (hourlyRate*reservationLength).toFixed(2);
|
||||||
|
|
||||||
|
const formattedDate = startMoment.clone().startOf('day').format('MMM DD, YYYY');
|
||||||
|
const formattedStartTime = startMoment.format('HH:mm a');
|
||||||
|
const formattedEndTime = endMoment.format('HH:mm a');
|
||||||
|
|
||||||
|
const formattedName = `[BOOKING FEES] ${officeName}, ${resourceName}, $${totalCost}, ${reservationLength.toFixed(2)} x $${hourlyRate.toFixed(2)}, ${formattedDate} [${formattedStartTime} - ${formattedEndTime}]`;
|
||||||
|
|
||||||
|
return {
|
||||||
|
name: formattedName,
|
||||||
|
price: hourlyRate,
|
||||||
|
quantity: reservationLength,
|
||||||
|
date: startMoment.startOf('day').toISOString(),
|
||||||
|
member: memberId,
|
||||||
|
team: null,
|
||||||
|
office: officeId,
|
||||||
|
isPersonal: false,
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const getMembersFeesForDateRange = (dateRange, memberIds) => {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
const collectData = [getAllIncidents(dateRange, memberIds), getActiveBookingsForMembersInDateRange(dateRange, memberIds), getResourceMappings(), fetchAllMembers()];
|
||||||
|
|
||||||
|
|
||||||
|
Promise.all(collectData)
|
||||||
|
.then((result) => {
|
||||||
|
const allIncidents = result[0];
|
||||||
|
const allActiveBookings = result[1];
|
||||||
|
const resourceMappings = result[2];
|
||||||
|
const membersList = result[3];
|
||||||
|
|
||||||
|
const memberIdTeamMappings = {};
|
||||||
|
membersList.forEach((member) => {
|
||||||
|
memberIdTeamMappings[member.memberId] = member.teamId;
|
||||||
|
});
|
||||||
|
|
||||||
|
const allFees = [];
|
||||||
|
|
||||||
|
allIncidents.forEach((incident) => allFees.push(createFeeFromIncident(incident)));
|
||||||
|
allActiveBookings.forEach((booking) => allFees.push(createFeeFromBooking(booking, resourceMappings)));
|
||||||
|
|
||||||
|
allFees.forEach((fee) => {
|
||||||
|
fee.team = memberIdTeamMappings[fee.member] || null;
|
||||||
|
});
|
||||||
|
|
||||||
|
resolve(allFees);
|
||||||
|
})
|
||||||
|
.catch((error) => {
|
||||||
|
console.log(error);
|
||||||
|
reject(error);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
getMembersFeesForDateRange,
|
||||||
|
};
|
||||||
@@ -10,7 +10,7 @@ const { incidentType, UI_TIMEZONE, DEFAULT_DATE_FORMAT, integrationServiceErrors
|
|||||||
const { fetchAllMembers } = require('../officeRnD/members');
|
const { fetchAllMembers } = require('../officeRnD/members');
|
||||||
const { fetchOffices, fetchResources } = require('../officeRnD/resources');
|
const { fetchOffices, fetchResources } = require('../officeRnD/resources');
|
||||||
|
|
||||||
const getUnlockedIncidents = (startDate, endDate, memberId) => {
|
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'];
|
||||||
|
|
||||||
const filters = {};
|
const filters = {};
|
||||||
@@ -41,8 +41,10 @@ const getUnlockedIncidents = (startDate, endDate, memberId) => {
|
|||||||
Object.assign(filters, bookingStartOrUnlockTimestamp);
|
Object.assign(filters, bookingStartOrUnlockTimestamp);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (memberId){
|
if (memberIds.length > 0){
|
||||||
filters.memberId = memberId;
|
filters.memberId = {
|
||||||
|
[Op.in]: memberIds
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
return db.unlockedIncident.findAll({
|
return db.unlockedIncident.findAll({
|
||||||
@@ -54,7 +56,7 @@ const getUnlockedIncidents = (startDate, endDate, memberId) => {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
const getUnscheduledIncidents = (startDate, endDate, memberId) => {
|
const getUnscheduledIncidents = (startDate, endDate, memberIds) => {
|
||||||
const attributes = [
|
const attributes = [
|
||||||
'id',
|
'id',
|
||||||
'reservationId',
|
'reservationId',
|
||||||
@@ -98,8 +100,10 @@ const getUnscheduledIncidents = (startDate, endDate, memberId) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
if (memberId){
|
if (memberIds.length > 0){
|
||||||
filters.memberId = memberId;
|
filters.memberId = {
|
||||||
|
[Op.in]: memberIds
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
return db.unscheduledIncident.findAll({
|
return db.unscheduledIncident.findAll({
|
||||||
@@ -111,7 +115,7 @@ const getUnscheduledIncidents = (startDate, endDate, memberId) => {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
const getBookingChangeIncidents = (startDate, endDate, memberId) => {
|
const getBookingChangeIncidents = (startDate, endDate, memberIds) => {
|
||||||
const attributes = [
|
const attributes = [
|
||||||
'id',
|
'id',
|
||||||
'reservationId',
|
'reservationId',
|
||||||
@@ -138,8 +142,10 @@ const getBookingChangeIncidents = (startDate, endDate, memberId) => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (memberId){
|
if (memberIds.length > 0){
|
||||||
filters.memberId = memberId;
|
filters.memberId = {
|
||||||
|
[Op.in]: memberIds
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
return db.bookingChangeIncident.findAll({
|
return db.bookingChangeIncident.findAll({
|
||||||
@@ -160,7 +166,7 @@ const formatTime = (timestamp) => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const getAllIncidents = (dateRange, memberId) => {
|
const getAllIncidents = (dateRange, memberIds) => {
|
||||||
return new Promise ((resolve, reject) => {
|
return new Promise ((resolve, reject) => {
|
||||||
let startDate, endDate;
|
let startDate, endDate;
|
||||||
|
|
||||||
@@ -178,9 +184,9 @@ const getAllIncidents = (dateRange, memberId) => {
|
|||||||
fetchAllMembers(),
|
fetchAllMembers(),
|
||||||
fetchOffices(),
|
fetchOffices(),
|
||||||
fetchResources(),
|
fetchResources(),
|
||||||
getUnlockedIncidents(startDate, endDate, memberId),
|
getUnlockedIncidents(startDate, endDate, memberIds),
|
||||||
getUnscheduledIncidents(startDate, endDate, memberId),
|
getUnscheduledIncidents(startDate, endDate, memberIds),
|
||||||
getBookingChangeIncidents(startDate, endDate, memberId)
|
getBookingChangeIncidents(startDate, endDate, memberIds)
|
||||||
];
|
];
|
||||||
|
|
||||||
Promise.all(dataFetchJobs)
|
Promise.all(dataFetchJobs)
|
||||||
@@ -210,10 +216,14 @@ const getAllIncidents = (dateRange, memberId) => {
|
|||||||
memberId: unlockedIncident.memberId,
|
memberId: unlockedIncident.memberId,
|
||||||
memberName: membersMap[unlockedIncident.memberId].name,
|
memberName: membersMap[unlockedIncident.memberId].name,
|
||||||
resourceName: resourcesMap[unlockedIncident.resourceId].resourceName,
|
resourceName: resourcesMap[unlockedIncident.resourceId].resourceName,
|
||||||
|
officeId: resourcesMap[unlockedIncident.resourceId].officeId,
|
||||||
officeName: officesMap[resourcesMap[unlockedIncident.resourceId].officeId].officeName,
|
officeName: officesMap[resourcesMap[unlockedIncident.resourceId].officeId].officeName,
|
||||||
bookingStart: formatTime(unlockedIncident.bookingStart),
|
bookingStart: formatTime(unlockedIncident.bookingStart),
|
||||||
bookingEnd: formatTime(unlockedIncident.bookingEnd),
|
bookingEnd: formatTime(unlockedIncident.bookingEnd),
|
||||||
|
bookingStartRaw: unlockedIncident.bookingStart,
|
||||||
|
bookingEndRaw: unlockedIncident.bookingEnd,
|
||||||
unlockTimestamp: formatTime(unlockedIncident.unlockTimestamp),
|
unlockTimestamp: formatTime(unlockedIncident.unlockTimestamp),
|
||||||
|
unlockTimestampRaw: unlockedIncident.unlockTimestamp,
|
||||||
incidentType: incidentTypeNumber,
|
incidentType: incidentTypeNumber,
|
||||||
incidentLevel: unlockedIncident.incidentLevel,
|
incidentLevel: unlockedIncident.incidentLevel,
|
||||||
incidentPrice: unlockedIncident.incidentLevelPrice,
|
incidentPrice: unlockedIncident.incidentLevelPrice,
|
||||||
@@ -236,11 +246,16 @@ const getAllIncidents = (dateRange, memberId) => {
|
|||||||
memberId: unscheduledIncident.memberId,
|
memberId: unscheduledIncident.memberId,
|
||||||
memberName: membersMap[unscheduledIncident.memberId].name,
|
memberName: membersMap[unscheduledIncident.memberId].name,
|
||||||
resourceName: resourcesMap[unscheduledIncident.resourceId].resourceName,
|
resourceName: resourcesMap[unscheduledIncident.resourceId].resourceName,
|
||||||
|
officeId: resourcesMap[unscheduledIncident.resourceId].officeId,
|
||||||
officeName: officesMap[resourcesMap[unscheduledIncident.resourceId].officeId].officeName,
|
officeName: officesMap[resourcesMap[unscheduledIncident.resourceId].officeId].officeName,
|
||||||
bookingStart: formatTime(unscheduledIncident.bookingStart),
|
bookingStart: formatTime(unscheduledIncident.bookingStart),
|
||||||
bookingEnd: formatTime(unscheduledIncident.bookingEnd),
|
bookingEnd: formatTime(unscheduledIncident.bookingEnd),
|
||||||
|
bookingStartRaw: unscheduledIncident.bookingStart,
|
||||||
|
bookingEndRaw: unscheduledIncident.bookingEnd,
|
||||||
unlockTimestamp: formatTime(unscheduledIncident.unlockTimestamp),
|
unlockTimestamp: formatTime(unscheduledIncident.unlockTimestamp),
|
||||||
lockTimestamp: formatTime(unscheduledIncident.lockTimestamp),
|
lockTimestamp: formatTime(unscheduledIncident.lockTimestamp),
|
||||||
|
unlockTimestampRaw: unscheduledIncident.unlockTimestamp,
|
||||||
|
lockTimestampRaw: unscheduledIncident.lockTimestamp,
|
||||||
incidentType: incidentTypeNumber,
|
incidentType: incidentTypeNumber,
|
||||||
timeIntervalsToCharge: unscheduledIncident.timeIntervalsToCharge,
|
timeIntervalsToCharge: unscheduledIncident.timeIntervalsToCharge,
|
||||||
chargePrice: unscheduledIncident.chargePrice,
|
chargePrice: unscheduledIncident.chargePrice,
|
||||||
@@ -267,21 +282,28 @@ const getAllIncidents = (dateRange, memberId) => {
|
|||||||
const newResource = newResourceId ? resourcesMap[newResourceId] : null;
|
const newResource = newResourceId ? resourcesMap[newResourceId] : null;
|
||||||
const oldResourceName = oldResource.resourceName;
|
const oldResourceName = oldResource.resourceName;
|
||||||
const newResourceName = newResource ? newResource.resourceName : null;
|
const newResourceName = newResource ? newResource.resourceName : null;
|
||||||
const officeName = officesMap[oldResource.officeId].officeName;
|
const officeId = oldResource.officeId;
|
||||||
|
const officeName = officesMap[officeId].officeName;
|
||||||
allIncidents.push({
|
allIncidents.push({
|
||||||
incidentId: id,
|
incidentId: id,
|
||||||
memberId,
|
memberId,
|
||||||
memberName,
|
memberName,
|
||||||
oldResourceName,
|
oldResourceName,
|
||||||
newResourceName,
|
newResourceName,
|
||||||
|
officeId,
|
||||||
officeName,
|
officeName,
|
||||||
oldBookingStart: formatTime(oldBookingStart),
|
oldBookingStart: formatTime(oldBookingStart),
|
||||||
oldBookingEnd: formatTime(oldBookingEnd),
|
oldBookingEnd: formatTime(oldBookingEnd),
|
||||||
newBookingStart: formatTime(newBookingStart),
|
newBookingStart: formatTime(newBookingStart),
|
||||||
newBookingEnd: formatTime(newBookingEnd),
|
newBookingEnd: formatTime(newBookingEnd),
|
||||||
|
oldBookingStartRaw: oldBookingStart,
|
||||||
|
oldBookingEndRaw: oldBookingEnd,
|
||||||
|
newBookingStartRaw: newBookingStart,
|
||||||
|
newBookingEndRaw: newBookingEnd,
|
||||||
incidentType,
|
incidentType,
|
||||||
totalChargeFee: chargeFee,
|
totalChargeFee: chargeFee,
|
||||||
incidentTimestamp: formatTime(createdAt),
|
incidentTimestamp: formatTime(createdAt),
|
||||||
|
incidentTimestampRaw: createdAt,
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -292,7 +314,5 @@ const getAllIncidents = (dateRange, memberId) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
getUnlockedIncidents,
|
|
||||||
getUnscheduledIncidents,
|
|
||||||
getAllIncidents,
|
getAllIncidents,
|
||||||
};
|
};
|
||||||
|
|||||||
70
services/officeRnD/fees.js
Normal file
70
services/officeRnD/fees.js
Normal file
@@ -0,0 +1,70 @@
|
|||||||
|
'use strict';
|
||||||
|
|
||||||
|
const moment = require('moment-timezone');
|
||||||
|
|
||||||
|
const { API } = require('../../helpers/api');
|
||||||
|
const { officeRnDAPIErrors, DEFAULT_DATE_FORMAT } = require('../../constants/constants');
|
||||||
|
|
||||||
|
const deleteFeesFromORD = (dateRange, memberIds) => {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
const startDate = moment.utc(dateRange.startDate, DEFAULT_DATE_FORMAT).startOf('day');
|
||||||
|
const endDate = moment.utc(dateRange.endDate, DEFAULT_DATE_FORMAT).endOf('day');
|
||||||
|
|
||||||
|
API.get('fees')
|
||||||
|
.then((response) => {
|
||||||
|
const fetchedFees = response.data ? response.data : [];
|
||||||
|
|
||||||
|
const memberIdsMap = {};
|
||||||
|
memberIds.forEach((memberId) => {
|
||||||
|
memberIdsMap[memberId] = true;
|
||||||
|
});
|
||||||
|
|
||||||
|
const deleteRequests = [];
|
||||||
|
const sendDeleteRequestPromise = (feeId) => {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
API.delete(`fees/${feeId}`)
|
||||||
|
.then(() => resolve(true))
|
||||||
|
.catch(() => resolve(false));
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
fetchedFees.forEach((fee) => {
|
||||||
|
const { member, date } = fee;
|
||||||
|
const feeId = fee['_id'];
|
||||||
|
|
||||||
|
const isDateInDateRange = startDate.isSameOrBefore(date) && endDate.isSameOrAfter(date);
|
||||||
|
if (memberIdsMap[member] && isDateInDateRange) {
|
||||||
|
deleteRequests.push(sendDeleteRequestPromise(feeId));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
Promise.all(deleteRequests)
|
||||||
|
.then(() => {
|
||||||
|
resolve(true);
|
||||||
|
})
|
||||||
|
.catch((error) => {
|
||||||
|
reject(error);
|
||||||
|
});
|
||||||
|
})
|
||||||
|
.catch((error) => {
|
||||||
|
console.log(error);
|
||||||
|
reject(officeRnDAPIErrors.FAILED_TO_FETCH_FEES);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const addFeesToORD = (allFees) => {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
API.post('/fees', allFees)
|
||||||
|
.catch((error) => {
|
||||||
|
console.log('==== ERROR ====');
|
||||||
|
console.log(error);
|
||||||
|
});
|
||||||
|
resolve(allFees.length);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
deleteFeesFromORD,
|
||||||
|
addFeesToORD
|
||||||
|
};
|
||||||
@@ -12,6 +12,7 @@ const fetchAllMembers = () => {
|
|||||||
cleanedResult.push({
|
cleanedResult.push({
|
||||||
name: member.name,
|
name: member.name,
|
||||||
memberId: member['_id'],
|
memberId: member['_id'],
|
||||||
|
teamId: member.team,
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
cleanedResult.sort((member1, member2) => (member1.name > member2.name) ? 1 : -1 );
|
cleanedResult.sort((member1, member2) => (member1.name > member2.name) ? 1 : -1 );
|
||||||
|
|||||||
@@ -45,6 +45,30 @@ const fetchResources = () => {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const getResourceMappings = () => {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
const fetchJobs = [fetchOffices(), fetchResources()];
|
||||||
|
|
||||||
|
Promise.all(fetchJobs)
|
||||||
|
.then((mappings) => {
|
||||||
|
const offices = mappings[0];
|
||||||
|
const resources = mappings[1];
|
||||||
|
|
||||||
|
const officesMap = {};
|
||||||
|
const resourcesMap = {};
|
||||||
|
|
||||||
|
offices.forEach((office) => officesMap[office.officeId] = office);
|
||||||
|
resources.forEach((resource) => resourcesMap[resource.resourceId] = resource);
|
||||||
|
|
||||||
|
resolve({
|
||||||
|
officesMap,
|
||||||
|
resourcesMap,
|
||||||
|
});
|
||||||
|
})
|
||||||
|
.catch((error) => reject(error));
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
const getMappingsFromDatabase = () => {
|
const getMappingsFromDatabase = () => {
|
||||||
return db.officeResourceMapping.findAll();
|
return db.officeResourceMapping.findAll();
|
||||||
};
|
};
|
||||||
@@ -57,5 +81,6 @@ module.exports = {
|
|||||||
getMappingsFromDatabase,
|
getMappingsFromDatabase,
|
||||||
fetchOffices,
|
fetchOffices,
|
||||||
fetchResources,
|
fetchResources,
|
||||||
|
getResourceMappings,
|
||||||
saveNewMappingToDatabase,
|
saveNewMappingToDatabase,
|
||||||
};
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user