From 5a6bc3cc6f808c5e80184a5d5ef71ea5c9bf500c Mon Sep 17 00:00:00 2001 From: Bilal Catic Date: Tue, 16 Jul 2019 11:22:40 +0200 Subject: [PATCH 1/7] add route and controller for invoice integration --- controllers/integration.js | 13 +++++++++++++ routes/index.js | 10 +++++++++- 2 files changed, 22 insertions(+), 1 deletion(-) diff --git a/controllers/integration.js b/controllers/integration.js index 235a774..b9ad18e 100644 --- a/controllers/integration.js +++ b/controllers/integration.js @@ -65,9 +65,22 @@ const getMemberIncidents = (req, res) => { }); }; +const addFees = (req, res) => { + const memberIds = req.body && req.body.memberIds ? req.body.memberIds : null; + const fromDate = req.body && req.body.fromDate ? req.body.fromDate : null; + const toDate = req.body && req.body.toDate ? req.body.toDate : null; + + if (fromDate && toDate){ + send(); + }else{ + res.status(400).send('Member ID missing'); + } +}; + module.exports = { getKnownOfficeResourceMappings, addNewMapping, getAllIncidentsController, getMemberIncidents, + addFees, }; diff --git a/routes/index.js b/routes/index.js index 0a6f01a..11efed8 100644 --- a/routes/index.js +++ b/routes/index.js @@ -2,8 +2,14 @@ const { apiStatusCheck } = require('../controllers/apiStatusCheck'); const { uploadDoorLockData } = require('../controllers/doorLock'); -const { getKnownOfficeResourceMappings, addNewMapping, getAllIncidentsController, getMemberIncidents } = require('../controllers/integration'); const { fetchMembersList } = require('../controllers/officeRnD'); +const { + getKnownOfficeResourceMappings, + addNewMapping, + getAllIncidentsController, + getMemberIncidents, + addFees +} = require('../controllers/integration'); const { calculateDoorLockCharges } = require('../services/integration/doorLockCharges'); @@ -21,6 +27,8 @@ router.get('/integration/report/allIncidents/:startDate/:endDate', getAllInciden router.get('/officeRnD/membersList', fetchMembersList); +router.post('/integration/addFees', addFees); + // temporary route, manually trigger door lock charge calculations router.get('/calculate', (req, res) => { calculateDoorLockCharges(); res.send();}); From fdd4491e0745e970bc5084fc24062988c4bb4bbf Mon Sep 17 00:00:00 2001 From: Bilal Catic Date: Tue, 16 Jul 2019 11:23:11 +0200 Subject: [PATCH 2/7] add button and modal dialog for invoice integration --- .../GenerateFeesInORDButton/index.js | 44 +++++++++++++++++++ client/src/scenes/IncidentsReport/index.js | 12 ++++- .../src/scenes/PracticeSummaryReport/index.js | 16 ++++++- 3 files changed, 70 insertions(+), 2 deletions(-) create mode 100644 client/src/components/GenerateFeesInORDButton/index.js diff --git a/client/src/components/GenerateFeesInORDButton/index.js b/client/src/components/GenerateFeesInORDButton/index.js new file mode 100644 index 0000000..9fae85c --- /dev/null +++ b/client/src/components/GenerateFeesInORDButton/index.js @@ -0,0 +1,44 @@ +import React, { Component } from 'react'; +import { Button, Modal } from 'semantic-ui-react'; + +class GenerateFeesInORDButton extends Component { + state = { open: false }; + + show = size => () => this.setState({ size, open: true }); + close = () => this.setState({ open: false }); + confirm = () => { + const { onConfirm } = this.props; + this.close(); + if (onConfirm){ + onConfirm(); + } + }; + + 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 ( +
+ + + + Add fees to the ORD + +

{modalContent}

+
+ + +
+ ); + } +} + +export default GenerateFeesInORDButton; diff --git a/client/src/scenes/IncidentsReport/index.js b/client/src/scenes/IncidentsReport/index.js index 3c4f766..0ce667c 100644 --- a/client/src/scenes/IncidentsReport/index.js +++ b/client/src/scenes/IncidentsReport/index.js @@ -5,6 +5,7 @@ import { Container } from 'semantic-ui-react'; import MainMenu from '../../components/MainMenu'; import DateRangePicker from '../../components/DateRangePicker'; import MemberIncidentsTables from '../../components/MemberIncidentsTables'; +import GenerateFeesInORDButton from '../../components/GenerateFeesInORDButton'; import { fetchIncidents } from '../../store/actions'; @@ -14,6 +15,8 @@ class IncidentsReport extends Component { fetchIncidents(dateRange); } + onAddFeesClick = () => {}; + render () { const { pendingIncidents, incidents } = this.props; @@ -22,7 +25,14 @@ class IncidentsReport extends Component {

Incidents Report


- + +
+ +

+

diff --git a/client/src/scenes/PracticeSummaryReport/index.js b/client/src/scenes/PracticeSummaryReport/index.js index e9a062b..379934b 100644 --- a/client/src/scenes/PracticeSummaryReport/index.js +++ b/client/src/scenes/PracticeSummaryReport/index.js @@ -7,6 +7,7 @@ import DateRangePicker from '../../components/DateRangePicker'; import MemberSelector from './components/MemberSelector'; import MemberSummary from './components/MemberSummary'; import MemberIncidentsTables from '../../components/MemberIncidentsTables'; +import GenerateFeesInORDButton from '../../components/GenerateFeesInORDButton'; import { fetchMemberIncidents } from '../../store/actions'; @@ -38,9 +39,13 @@ class PracticeSummaryReport extends Component { } } + onAddFeesClick = () => {}; + render () { const { memberIncidents, loading } = this.props; - const { memberId } = this.state; + const { memberId, dateRange } = this.state; + + const addFeesButtonDisabled = !memberId || !dateRange || loading; return ( @@ -64,6 +69,15 @@ class PracticeSummaryReport extends Component { /> + + + + + From 7437972a53b375baba7a7525a742d642d7eef5b9 Mon Sep 17 00:00:00 2001 From: Bilal Catic Date: Thu, 18 Jul 2019 03:44:16 +0200 Subject: [PATCH 3/7] send request to add fees in ORD --- .../GenerateFeesInORDButton/index.js | 19 +++++++++++++----- client/src/scenes/IncidentsReport/index.js | 20 +++++++++++-------- .../src/scenes/PracticeSummaryReport/index.js | 7 +++---- .../src/store/actions/integrationActions.js | 17 ++++++++++++++++ client/src/store/constants.js | 4 ++++ 5 files changed, 50 insertions(+), 17 deletions(-) diff --git a/client/src/components/GenerateFeesInORDButton/index.js b/client/src/components/GenerateFeesInORDButton/index.js index 9fae85c..43e6d0f 100644 --- a/client/src/components/GenerateFeesInORDButton/index.js +++ b/client/src/components/GenerateFeesInORDButton/index.js @@ -1,17 +1,22 @@ 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 { onConfirm } = this.props; - this.close(); - if (onConfirm){ - onConfirm(); + const { addFeesToOrd, dateRange, memberIds } = this.props; + + if (dateRange){ + addFeesToOrd(dateRange, memberIds); } + + this.close(); }; render() { @@ -41,4 +46,8 @@ class GenerateFeesInORDButton extends Component { } } -export default GenerateFeesInORDButton; +const mapDispatchToProps = (dispatch) => ({ + addFeesToOrd: (dateRange, memberIds) => addFeesToOrd(dispatch, dateRange, memberIds), +}); + +export default connect(null, mapDispatchToProps)(GenerateFeesInORDButton); diff --git a/client/src/scenes/IncidentsReport/index.js b/client/src/scenes/IncidentsReport/index.js index 0ce667c..0dc98fc 100644 --- a/client/src/scenes/IncidentsReport/index.js +++ b/client/src/scenes/IncidentsReport/index.js @@ -7,29 +7,32 @@ import DateRangePicker from '../../components/DateRangePicker'; 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 { - onDatesUpdate(dateRange) { - const { fetchIncidents } = this.props; - fetchIncidents(dateRange); - } + state = {dateRange: null}; - onAddFeesClick = () => {}; + onDatesUpdate = (dateRange) => { + const { fetchIncidents } = this.props; + + this.setState({dateRange}); + fetchIncidents(dateRange); + }; render () { const { pendingIncidents, incidents } = this.props; + const { dateRange } = this.state; return (

Incidents Report


- +



@@ -47,6 +50,7 @@ const mapStateToProps = (state) => ({ const mapDispatchToProps = (dispatch) => ({ fetchIncidents: (dateRange) => fetchIncidents(dispatch, dateRange), + addFeesToOrd: (dateRange, memberIds) => addFeesToOrd(dispatch, dateRange, memberIds), }); export default connect(mapStateToProps, mapDispatchToProps)(IncidentsReport); diff --git a/client/src/scenes/PracticeSummaryReport/index.js b/client/src/scenes/PracticeSummaryReport/index.js index 379934b..a3d6488 100644 --- a/client/src/scenes/PracticeSummaryReport/index.js +++ b/client/src/scenes/PracticeSummaryReport/index.js @@ -9,7 +9,7 @@ import MemberSummary from './components/MemberSummary'; import MemberIncidentsTables from '../../components/MemberIncidentsTables'; import GenerateFeesInORDButton from '../../components/GenerateFeesInORDButton'; -import { fetchMemberIncidents } from '../../store/actions'; +import { fetchMemberIncidents, addFeesToOrd } from '../../store/actions'; class PracticeSummaryReport extends Component { constructor(props){ @@ -39,8 +39,6 @@ class PracticeSummaryReport extends Component { } } - onAddFeesClick = () => {}; - render () { const { memberIncidents, loading } = this.props; const { memberId, dateRange } = this.state; @@ -74,7 +72,8 @@ class PracticeSummaryReport extends Component {
diff --git a/client/src/store/actions/integrationActions.js b/client/src/store/actions/integrationActions.js index 5e9a127..79a9068 100644 --- a/client/src/store/actions/integrationActions.js +++ b/client/src/store/actions/integrationActions.js @@ -14,6 +14,9 @@ import { FETCH_MEMBER_INCIDENTS_PENDING, FETCH_MEMBER_INCIDENTS_SUCCESS, FETCH_MEMBER_INCIDENTS_FAILED, + ADD_FEES_TO_ORD_PENDING, + ADD_FEES_TO_ORD_SUCCESS, + ADD_FEES_TO_ORD_FAILED, } from '../constants'; import API from '../../utilities/api'; @@ -78,3 +81,17 @@ export const fetchMemberIncidents = (dispatch, memberId, dateRange) => { 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}); + }); +}; diff --git a/client/src/store/constants.js b/client/src/store/constants.js index 6dd4fd8..6d67aef 100644 --- a/client/src/store/constants.js +++ b/client/src/store/constants.js @@ -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_SUCCESS = 'FETCH_MEMBER_INCIDENTS_SUCCESS'; 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'; From aeffe863133e7bebb0c06faa331b3992a73c1e01 Mon Sep 17 00:00:00 2001 From: Bilal Catic Date: Thu, 18 Jul 2019 03:45:39 +0200 Subject: [PATCH 4/7] add router for "addFees" endpoint --- controllers/integration.js | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/controllers/integration.js b/controllers/integration.js index b9ad18e..524e1c0 100644 --- a/controllers/integration.js +++ b/controllers/integration.js @@ -66,14 +66,14 @@ const getMemberIncidents = (req, res) => { }; const addFees = (req, res) => { - const memberIds = req.body && req.body.memberIds ? req.body.memberIds : null; - const fromDate = req.body && req.body.fromDate ? req.body.fromDate : null; - const toDate = req.body && req.body.toDate ? req.body.toDate : null; + 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 (fromDate && toDate){ - send(); + if (startDate && endDate){ + res.send(); }else{ - res.status(400).send('Member ID missing'); + res.status(400).send('Date range is missing'); } }; From 71e034b6c2baec0de030faf4af4c9bddd8a18380 Mon Sep 17 00:00:00 2001 From: Bilal Catic Date: Thu, 25 Jul 2019 02:00:27 +0200 Subject: [PATCH 5/7] invoice integration --- client/src/scenes/IncidentsReport/index.js | 9 + constants/constants.js | 34 ++- controllers/integration.js | 42 +++- cronServices/checkBookingChanges.js | 39 +-- services/integration/bookingChangeCharges.js | 2 +- services/integration/bookings.js | 53 ++++ services/integration/checkBookingChange.js | 44 ++++ services/integration/invoiceIntegration.js | 240 +++++++++++++++++++ services/integration/reports.js | 52 ++-- services/officeRnD/fees.js | 72 ++++++ services/officeRnD/members.js | 1 + services/officeRnD/resources.js | 25 ++ 12 files changed, 548 insertions(+), 65 deletions(-) create mode 100644 services/integration/bookings.js create mode 100644 services/integration/checkBookingChange.js create mode 100644 services/integration/invoiceIntegration.js create mode 100644 services/officeRnD/fees.js diff --git a/client/src/scenes/IncidentsReport/index.js b/client/src/scenes/IncidentsReport/index.js index 0dc98fc..6f952df 100644 --- a/client/src/scenes/IncidentsReport/index.js +++ b/client/src/scenes/IncidentsReport/index.js @@ -23,6 +23,14 @@ class IncidentsReport extends Component { const { pendingIncidents, incidents } = this.props; const { dateRange } = this.state; + const membersMap = {}; + if (incidents && Array.isArray(incidents)) { + incidents.forEach((incident) => { + membersMap[incident.memberId] = true; + }); + } + const memberIds = Object.keys(membersMap) || []; + return ( @@ -31,6 +39,7 @@ class IncidentsReport extends Component {
diff --git a/constants/constants.js b/constants/constants.js index d339663..0388308 100644 --- a/constants/constants.js +++ b/constants/constants.js @@ -13,32 +13,38 @@ const unlockedIncidentLevelsPrices = { UNLOCKED_0: { id: 0, title: 'UNLOCKED_0', - price: parseInt(process.env.UNLOCK_0) || 0 + price: parseInt(process.env.UNLOCK_0) || 0, + description: 'First month - warning', }, UNLOCKED_1: { id: 1, title: 'UNLOCKED_1', - price: parseInt(process.env.UNLOCK_1) || 10 + price: parseInt(process.env.UNLOCK_1) || 10, + description: 'Second month', }, UNLOCKED_2: { id: 2, title: 'UNLOCKED_2', - price: parseInt(process.env.UNLOCK_2) || 20 + price: parseInt(process.env.UNLOCK_2) || 20, + description: 'Third month', }, UNLOCKED_3: { id: 3, title: 'UNLOCKED_3', - price: parseInt(process.env.UNLOCK_3) || 30 + price: parseInt(process.env.UNLOCK_3) || 30, + description: 'Fourth month', }, UNLOCKED_4: { id: 4, title: 'UNLOCKED_4', - price: parseInt(process.env.UNLOCK_4) || 40 + price: parseInt(process.env.UNLOCK_4) || 40, + description: 'Fifth month', }, UNLOCKED_5: { id: 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 = { @@ -50,7 +56,10 @@ const csvParserErrors = { }; const officeRnDAPIErrors = { 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 = { FAILED_TO_SAVE_BOOKINGS: 'Failed to save booking reservations', @@ -71,6 +80,16 @@ const incidentType = { 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 DEFAULT_DATE_FORMAT = 'YYYY-MM-DD'; @@ -94,6 +113,7 @@ module.exports = { unlockedIncidentLevelsPrices, integrationServiceErrors, incidentType, + incidentTypeExplanations, UI_TIMEZONE, DEFAULT_DATE_FORMAT, MAX_BACK_TO_BACK_DIFFERENCE, diff --git a/controllers/integration.js b/controllers/integration.js index 524e1c0..ffe2ef0 100644 --- a/controllers/integration.js +++ b/controllers/integration.js @@ -2,6 +2,9 @@ const { getMappingsFromDatabase, fetchOffices, fetchResources, saveNewMappingToDatabase } = require('../services/officeRnD/resources'); 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 dataToFetch = [getMappingsFromDatabase(), fetchOffices(), fetchResources() ]; @@ -38,7 +41,7 @@ const getAllIncidentsController = (req, res) => { endDate: req.params.endDate, }; - getAllIncidents(dateRange) + getAllIncidents(dateRange, []) .then((incidents) => { res.send(incidents); }) @@ -55,7 +58,7 @@ const getMemberIncidents = (req, res) => { endDate: req.params.endDate, }; - getAllIncidents(dateRange, memberId) + getAllIncidents(dateRange, [memberId]) .then((incidents) => { res.send(incidents); }) @@ -70,8 +73,39 @@ const addFees = (req, res) => { const dateRange = req.body && req.body.dateRange ? req.body.dateRange : {startDate: null, endDate: null}; const { startDate, endDate } = dateRange; - if (startDate && endDate){ - res.send(); + 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'); } diff --git a/cronServices/checkBookingChanges.js b/cronServices/checkBookingChanges.js index aeae234..a4a2e37 100644 --- a/cronServices/checkBookingChanges.js +++ b/cronServices/checkBookingChanges.js @@ -1,41 +1,6 @@ 'use strict'; require('dotenv').config(); -const { fetchAllBookings, bulkWriteReservationsWithChangesTracking } = require('../services/officeRnD/bookings'); +const { checkBookingChanges } = require('../services/integration/checkBookingChange'); -const { chargeBookingChanges } = require('../services/integration/bookingChangeCharges'); -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(); +checkBookingChanges().then(() => process.exit()).catch(() => process.exit()); diff --git a/services/integration/bookingChangeCharges.js b/services/integration/bookingChangeCharges.js index cbe26c4..1906707 100644 --- a/services/integration/bookingChangeCharges.js +++ b/services/integration/bookingChangeCharges.js @@ -97,7 +97,7 @@ const chargeBookingChanges = (changes) => { incidents.push(incident); } }else{ - const differenceFromCreation = reservationCreationTimestamp.diff(moment.utc(), 'minutes'); + const differenceFromCreation = moment.utc().diff(reservationCreationTimestamp, 'minutes'); if (differenceFromCreation > ALLOWED_BOOKING_CANCELLATION_TIME){ const chargeFee = 2 * reservationHourlyRate * oldReservationLength * BOOKING_CHANGE_PERCENTAGE_CHARGE / 100; diff --git a/services/integration/bookings.js b/services/integration/bookings.js new file mode 100644 index 0000000..9e69131 --- /dev/null +++ b/services/integration/bookings.js @@ -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, +}; diff --git a/services/integration/checkBookingChange.js b/services/integration/checkBookingChange.js new file mode 100644 index 0000000..3606c3e --- /dev/null +++ b/services/integration/checkBookingChange.js @@ -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 +}; diff --git a/services/integration/invoiceIntegration.js b/services/integration/invoiceIntegration.js new file mode 100644 index 0000000..b7b29d5 --- /dev/null +++ b/services/integration/invoiceIntegration.js @@ -0,0 +1,240 @@ +'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 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; + + 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')}`; + + 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')}`; + + 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; + + 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 = ''; + + 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(DEFAULT_DATE_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(DEFAULT_DATE_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(DEFAULT_DATE_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: moment.tz(incidentTimestampRaw, UI_TIMEZONE).startOf('day').toISOString(), + 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, +}; diff --git a/services/integration/reports.js b/services/integration/reports.js index 4bd54ca..ccab8d1 100644 --- a/services/integration/reports.js +++ b/services/integration/reports.js @@ -10,7 +10,7 @@ const { incidentType, UI_TIMEZONE, DEFAULT_DATE_FORMAT, integrationServiceErrors const { fetchAllMembers } = require('../officeRnD/members'); 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 filters = {}; @@ -41,8 +41,10 @@ const getUnlockedIncidents = (startDate, endDate, memberId) => { Object.assign(filters, bookingStartOrUnlockTimestamp); } - if (memberId){ - filters.memberId = memberId; + if (memberIds.length > 0){ + filters.memberId = { + [Op.in]: memberIds + }; } 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 = [ 'id', 'reservationId', @@ -98,8 +100,10 @@ const getUnscheduledIncidents = (startDate, endDate, memberId) => { } - if (memberId){ - filters.memberId = memberId; + if (memberIds.length > 0){ + filters.memberId = { + [Op.in]: memberIds + }; } 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 = [ 'id', 'reservationId', @@ -138,8 +142,10 @@ const getBookingChangeIncidents = (startDate, endDate, memberId) => { } } - if (memberId){ - filters.memberId = memberId; + if (memberIds.length > 0){ + filters.memberId = { + [Op.in]: memberIds + }; } return db.bookingChangeIncident.findAll({ @@ -160,7 +166,7 @@ const formatTime = (timestamp) => { } }; -const getAllIncidents = (dateRange, memberId) => { +const getAllIncidents = (dateRange, memberIds) => { return new Promise ((resolve, reject) => { let startDate, endDate; @@ -178,9 +184,9 @@ const getAllIncidents = (dateRange, memberId) => { fetchAllMembers(), fetchOffices(), fetchResources(), - getUnlockedIncidents(startDate, endDate, memberId), - getUnscheduledIncidents(startDate, endDate, memberId), - getBookingChangeIncidents(startDate, endDate, memberId) + getUnlockedIncidents(startDate, endDate, memberIds), + getUnscheduledIncidents(startDate, endDate, memberIds), + getBookingChangeIncidents(startDate, endDate, memberIds) ]; Promise.all(dataFetchJobs) @@ -210,10 +216,14 @@ const getAllIncidents = (dateRange, memberId) => { memberId: unlockedIncident.memberId, memberName: membersMap[unlockedIncident.memberId].name, resourceName: resourcesMap[unlockedIncident.resourceId].resourceName, + officeId: resourcesMap[unlockedIncident.resourceId].officeId, officeName: officesMap[resourcesMap[unlockedIncident.resourceId].officeId].officeName, bookingStart: formatTime(unlockedIncident.bookingStart), bookingEnd: formatTime(unlockedIncident.bookingEnd), + bookingStartRaw: unlockedIncident.bookingStart, + bookingEndRaw: unlockedIncident.bookingEnd, unlockTimestamp: formatTime(unlockedIncident.unlockTimestamp), + unlockTimestampRaw: unlockedIncident.unlockTimestamp, incidentType: incidentTypeNumber, incidentLevel: unlockedIncident.incidentLevel, incidentPrice: unlockedIncident.incidentLevelPrice, @@ -236,11 +246,16 @@ const getAllIncidents = (dateRange, memberId) => { memberId: unscheduledIncident.memberId, memberName: membersMap[unscheduledIncident.memberId].name, resourceName: resourcesMap[unscheduledIncident.resourceId].resourceName, + officeId: resourcesMap[unscheduledIncident.resourceId].officeId, officeName: officesMap[resourcesMap[unscheduledIncident.resourceId].officeId].officeName, bookingStart: formatTime(unscheduledIncident.bookingStart), bookingEnd: formatTime(unscheduledIncident.bookingEnd), + bookingStartRaw: unscheduledIncident.bookingStart, + bookingEndRaw: unscheduledIncident.bookingEnd, unlockTimestamp: formatTime(unscheduledIncident.unlockTimestamp), lockTimestamp: formatTime(unscheduledIncident.lockTimestamp), + unlockTimestampRaw: unscheduledIncident.unlockTimestamp, + lockTimestampRaw: unscheduledIncident.lockTimestamp, incidentType: incidentTypeNumber, timeIntervalsToCharge: unscheduledIncident.timeIntervalsToCharge, chargePrice: unscheduledIncident.chargePrice, @@ -267,21 +282,28 @@ const getAllIncidents = (dateRange, memberId) => { const newResource = newResourceId ? resourcesMap[newResourceId] : null; const oldResourceName = oldResource.resourceName; const newResourceName = newResource ? newResource.resourceName : null; - const officeName = officesMap[oldResource.officeId].officeName; + const officeId = oldResource.officeId; + const officeName = officesMap[officeId].officeName; allIncidents.push({ incidentId: id, memberId, memberName, oldResourceName, newResourceName, + officeId, officeName, oldBookingStart: formatTime(oldBookingStart), oldBookingEnd: formatTime(oldBookingEnd), newBookingStart: formatTime(newBookingStart), newBookingEnd: formatTime(newBookingEnd), + oldBookingStartRaw: oldBookingStart, + oldBookingEndRaw: oldBookingEnd, + newBookingStartRaw: newBookingStart, + newBookingEndRaw: newBookingEnd, incidentType, totalChargeFee: chargeFee, incidentTimestamp: formatTime(createdAt), + incidentTimestampRaw: createdAt, }); }); @@ -292,7 +314,5 @@ const getAllIncidents = (dateRange, memberId) => { }; module.exports = { - getUnlockedIncidents, - getUnscheduledIncidents, getAllIncidents, }; diff --git a/services/officeRnD/fees.js b/services/officeRnD/fees.js new file mode 100644 index 0000000..ad41162 --- /dev/null +++ b/services/officeRnD/fees.js @@ -0,0 +1,72 @@ +'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']; + + if (memberIdsMap[member]) { + 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 +}; diff --git a/services/officeRnD/members.js b/services/officeRnD/members.js index 6c804d3..42bbe1d 100644 --- a/services/officeRnD/members.js +++ b/services/officeRnD/members.js @@ -12,6 +12,7 @@ const fetchAllMembers = () => { cleanedResult.push({ name: member.name, memberId: member['_id'], + teamId: member.team, }); }); cleanedResult.sort((member1, member2) => (member1.name > member2.name) ? 1 : -1 ); diff --git a/services/officeRnD/resources.js b/services/officeRnD/resources.js index 95b1d64..37d73b7 100644 --- a/services/officeRnD/resources.js +++ b/services/officeRnD/resources.js @@ -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 = () => { return db.officeResourceMapping.findAll(); }; @@ -57,5 +81,6 @@ module.exports = { getMappingsFromDatabase, fetchOffices, fetchResources, + getResourceMappings, saveNewMappingToDatabase, }; From a5221da2f10140eb093f6415a1fc1dccfe46fb6b Mon Sep 17 00:00:00 2001 From: Bilal Catic Date: Thu, 25 Jul 2019 02:36:38 +0200 Subject: [PATCH 6/7] fix incidents date --- services/integration/invoiceIntegration.js | 25 ++++++++++++++++++---- services/officeRnD/fees.js | 8 +++---- 2 files changed, 24 insertions(+), 9 deletions(-) diff --git a/services/integration/invoiceIntegration.js b/services/integration/invoiceIntegration.js index b7b29d5..b54e875 100644 --- a/services/integration/invoiceIntegration.js +++ b/services/integration/invoiceIntegration.js @@ -36,6 +36,7 @@ const createFeeFromIncident = (incident) => { const incidentExplanation = incidentTypeExplanations[incidentTypeNumber]; + let date = ''; let price = 0; let quantity = 0; let priceExplanation = ''; @@ -67,6 +68,8 @@ const createFeeFromIncident = (incident) => { 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)}`; @@ -77,6 +80,8 @@ const createFeeFromIncident = (incident) => { 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)}`; @@ -87,6 +92,8 @@ const createFeeFromIncident = (incident) => { 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)}`; @@ -98,6 +105,8 @@ const createFeeFromIncident = (incident) => { 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)}`; @@ -109,6 +118,8 @@ const createFeeFromIncident = (incident) => { 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)}`; @@ -122,7 +133,9 @@ const createFeeFromIncident = (incident) => { 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(DEFAULT_DATE_FORMAT)}`; + incidentTimeExplanation = `MOVED ON : ${incidentTimestampMoment.clone().format('MMM DD, YYYY')}`; + + date = incidentTimestampMoment.clone().startOf('day').format(); price = totalChargeFee; quantity = 1; @@ -137,7 +150,9 @@ const createFeeFromIncident = (incident) => { 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(DEFAULT_DATE_FORMAT)}`; + incidentTimeExplanation = `SHORTENED ON : ${incidentTimestampMoment.clone().format('MMM DD, YYYY')}`; + + date = incidentTimestampMoment.clone().startOf('day').format(); price = totalChargeFee; quantity = 1; @@ -147,7 +162,9 @@ const createFeeFromIncident = (incident) => { 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(DEFAULT_DATE_FORMAT)}`; + incidentTimeExplanation = `CANCELED ON : ${incidentTimestampMoment.clone().format('MMM DD, YYYY')}`; + + date = incidentTimestampMoment.clone().startOf('day').format(); price = totalChargeFee; quantity = 1; @@ -161,7 +178,7 @@ const createFeeFromIncident = (incident) => { name: formattedName, price, quantity, - date: moment.tz(incidentTimestampRaw, UI_TIMEZONE).startOf('day').toISOString(), + date, member: memberId, team: null, office: officeId, diff --git a/services/officeRnD/fees.js b/services/officeRnD/fees.js index ad41162..dd93131 100644 --- a/services/officeRnD/fees.js +++ b/services/officeRnD/fees.js @@ -32,11 +32,9 @@ const deleteFeesFromORD = (dateRange, memberIds) => { const { member, date } = fee; const feeId = fee['_id']; - if (memberIdsMap[member]) { - const isDateInDateRange = startDate.isSameOrBefore(date) && endDate.isSameOrAfter(date); - if (memberIdsMap[member] && isDateInDateRange) { - deleteRequests.push(sendDeleteRequestPromise(feeId)); - } + const isDateInDateRange = startDate.isSameOrBefore(date) && endDate.isSameOrAfter(date); + if (memberIdsMap[member] && isDateInDateRange) { + deleteRequests.push(sendDeleteRequestPromise(feeId)); } }); From 84befa07aebecea06dbac1556343a6ca0fdf6bac Mon Sep 17 00:00:00 2001 From: Bilal Catic Date: Thu, 25 Jul 2019 03:01:09 +0200 Subject: [PATCH 7/7] add loading while adding fees to ORD --- client/src/scenes/IncidentsReport/index.js | 9 +++-- .../src/scenes/PracticeSummaryReport/index.js | 11 ++++-- .../src/store/reducers/addFeesToOrdReducer.js | 38 +++++++++++++++++++ client/src/store/reducers/index.js | 2 + 4 files changed, 53 insertions(+), 7 deletions(-) create mode 100644 client/src/store/reducers/addFeesToOrdReducer.js diff --git a/client/src/scenes/IncidentsReport/index.js b/client/src/scenes/IncidentsReport/index.js index 6f952df..40dad4b 100644 --- a/client/src/scenes/IncidentsReport/index.js +++ b/client/src/scenes/IncidentsReport/index.js @@ -20,9 +20,11 @@ class IncidentsReport extends Component { }; 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) => { @@ -40,13 +42,13 @@ class IncidentsReport extends Component {




- +
); } @@ -55,6 +57,7 @@ class IncidentsReport extends Component { const mapStateToProps = (state) => ({ pendingIncidents: state.incidentsReport.pending, incidents: state.incidentsReport.result, + pendingAddFeesStatus: state.addFeesStatus.pending, }); const mapDispatchToProps = (dispatch) => ({ diff --git a/client/src/scenes/PracticeSummaryReport/index.js b/client/src/scenes/PracticeSummaryReport/index.js index a3d6488..b0df6b8 100644 --- a/client/src/scenes/PracticeSummaryReport/index.js +++ b/client/src/scenes/PracticeSummaryReport/index.js @@ -1,6 +1,6 @@ import React, { Component } from 'react'; 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 DateRangePicker from '../../components/DateRangePicker'; @@ -9,7 +9,7 @@ import MemberSummary from './components/MemberSummary'; import MemberIncidentsTables from '../../components/MemberIncidentsTables'; import GenerateFeesInORDButton from '../../components/GenerateFeesInORDButton'; -import { fetchMemberIncidents, addFeesToOrd } from '../../store/actions'; +import { fetchMemberIncidents } from '../../store/actions'; class PracticeSummaryReport extends Component { constructor(props){ @@ -40,9 +40,11 @@ class PracticeSummaryReport extends Component { } render () { - const { memberIncidents, loading } = this.props; + const { memberIncidents, loadingMemberIncidents, loadingAddFeesStatus } = this.props; const { memberId, dateRange } = this.state; + const loading = loadingAddFeesStatus || loadingMemberIncidents; + const addFeesButtonDisabled = !memberId || !dateRange || loading; return ( @@ -91,7 +93,8 @@ class PracticeSummaryReport extends Component { const mapStateToProps = (state) => ({ memberIncidents: state.memberIncidents.result, - loading: state.memberIncidents.pending, + loadingMemberIncidents: state.memberIncidents.pending, + loadingAddFeesStatus: state.addFeesStatus.pending, }); const mapDispatchToProps = (dispatch) => ({ diff --git a/client/src/store/reducers/addFeesToOrdReducer.js b/client/src/store/reducers/addFeesToOrdReducer.js new file mode 100644 index 0000000..402cdca --- /dev/null +++ b/client/src/store/reducers/addFeesToOrdReducer.js @@ -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; + } +}; diff --git a/client/src/store/reducers/index.js b/client/src/store/reducers/index.js index 869300c..3eb9e9f 100644 --- a/client/src/store/reducers/index.js +++ b/client/src/store/reducers/index.js @@ -6,6 +6,7 @@ import { addMapping } from './addMappingReducer'; import { incidentsReport } from './incidentsReportReducer'; import { membersList } from './membersListReducer'; import { memberIncidents} from './memberIncidentsReducer'; +import { addFeesStatus } from './addFeesToOrdReducer'; export const rootReducer = combineReducers({ doorLockData, @@ -14,5 +15,6 @@ export const rootReducer = combineReducers({ incidentsReport, membersList, memberIncidents, + addFeesStatus, });