diff --git a/client/src/components/MemberIncidentsTables/components/SingleIncidentsTable.js b/client/src/components/MemberIncidentsTables/components/SingleIncidentsTable.js index c4c2221..26d83a9 100644 --- a/client/src/components/MemberIncidentsTables/components/SingleIncidentsTable.js +++ b/client/src/components/MemberIncidentsTables/components/SingleIncidentsTable.js @@ -12,16 +12,21 @@ import { incidentDescriptions, incidentLevelDescriptions, UNLOCKED_INCIDENT_RELATED_WITH_RESERVATION, UNLOCKED_INCIDENT_STANDALONE, UNSCHEDULED_INCIDENT_AFTER_RESERVATION, - UNSCHEDULED_INCIDENT_BEFORE_RESERVATION, UNSCHEDULED_INCIDENT_STANDALONE + UNSCHEDULED_INCIDENT_BEFORE_RESERVATION, UNSCHEDULED_INCIDENT_STANDALONE, BOOKING_CANCELED_LATE, BOOKING_MOVED_TO_ANOTHER_DAY, + BOOKING_SHORTENED } from '../../../constants/enums'; import { doorLockRelatedWithReservationIncidentHeaders, standaloneDoorLockIncidentHeaders, bookingChangeIncidentHeaders } from '../../../constants/constants'; -import { deleteIncidents } from "../../../store/actions"; +import { deleteIncidents, updateIncidentFees } from "../../../store/actions"; class SingleIncidentsTable extends Component { state = { selectedUnlockedIncidentIds: [], selectedUnscheduledIncidentIds: [], - selectedBookingChangeIncidentIds: [] + selectedBookingChangeIncidentIds: [], + changedUnlockedIncidentIds: {}, + changedUnscheduledIncidentIds: {}, + changedBookingChangeIncidentIds: {}, + inputIdToFocus: null, }; onSelectChange = (selectedIncidents) => { @@ -82,6 +87,24 @@ class SingleIncidentsTable extends Component { }); }; + updateChangedFees = () => { + const { dateRange, updateIncidentsById, memberId } = this.props; + const { changedUnlockedIncidentIds, changedUnscheduledIncidentIds, changedBookingChangeIncidentIds } = this.state; + + const incidentFeesToUpdate = { + unlockedIncidentFees: changedUnlockedIncidentIds, + unscheduledIncidentFees: changedUnscheduledIncidentIds, + bookingChangeIncidentFees: changedBookingChangeIncidentIds + }; + + updateIncidentsById(dateRange, incidentFeesToUpdate, memberId); + this.setState({ + changedUnlockedIncidentIds: {}, + changedUnscheduledIncidentIds: {}, + changedBookingChangeIncidentIds: {} + }); + }; + render(){ const { loading, @@ -94,12 +117,22 @@ class SingleIncidentsTable extends Component { const { selectedUnlockedIncidentIds, selectedUnscheduledIncidentIds, - selectedBookingChangeIncidentIds + selectedBookingChangeIncidentIds, + changedUnlockedIncidentIds, + changedUnscheduledIncidentIds, + changedBookingChangeIncidentIds, + inputIdToFocus } = this.state; const totalSelected = selectedUnlockedIncidentIds.length + selectedUnscheduledIncidentIds.length + selectedBookingChangeIncidentIds.length; const numberOfSelectedText = totalSelected > 0 ? ` (${totalSelected})` : ''; + const totalChanged = + Object.keys(changedUnlockedIncidentIds).length + + Object.keys(changedUnscheduledIncidentIds).length + + Object.keys(changedBookingChangeIncidentIds).length; + const numberOfChangedText = totalChanged > 0 ? ` (${totalChanged})` : ''; + const incidents = this.props.incidents ? this.props.incidents : []; incidents.forEach(incident => { incident.id = `${incident.incidentType}-${incident.incidentId}`; @@ -122,6 +155,44 @@ class SingleIncidentsTable extends Component { break; } + const priceChangeHandler = (element) => { + if (element && element.target){ + const {value: newValue, id: incidentDescriptionID} = element.target; + const newValueFloat = parseFloat(newValue); + const incidentData = incidentDescriptionID.split('-'); + const incidentType = parseInt(incidentData[0]) || null; + const incidentID = parseInt(incidentData[1]) || null; + + if ((newValueFloat || (newValueFloat === 0) || (isNaN(newValueFloat))) && incidentType && incidentID){ + const changedUnlockedIncidentIdsCopy = Object.assign({}, changedUnlockedIncidentIds); + const changedUnscheduledIncidentIdsCopy = Object.assign({}, changedUnscheduledIncidentIds); + const changedBookingChangeIncidentIdsCopy = Object.assign({}, changedBookingChangeIncidentIds); + + switch (incidentType) { + case UNLOCKED_INCIDENT_RELATED_WITH_RESERVATION: + case UNLOCKED_INCIDENT_STANDALONE: + changedUnlockedIncidentIdsCopy[incidentID] = newValueFloat; + this.setState({changedUnlockedIncidentIds: changedUnlockedIncidentIdsCopy, inputIdToFocus: incidentDescriptionID}); + break; + + case UNSCHEDULED_INCIDENT_BEFORE_RESERVATION: + case UNSCHEDULED_INCIDENT_AFTER_RESERVATION: + case UNSCHEDULED_INCIDENT_STANDALONE: + changedUnscheduledIncidentIdsCopy[incidentID] = newValueFloat; + this.setState({changedUnscheduledIncidentIds: changedUnscheduledIncidentIdsCopy, inputIdToFocus: incidentDescriptionID}); + break; + + case BOOKING_MOVED_TO_ANOTHER_DAY: + case BOOKING_SHORTENED: + case BOOKING_CANCELED_LATE: + changedBookingChangeIncidentIdsCopy[incidentID] = newValueFloat; + this.setState({changedBookingChangeIncidentIds: changedBookingChangeIncidentIdsCopy, inputIdToFocus: incidentDescriptionID}); + break; + } + } + } + }; + tableHeaders.forEach((header) => { const columnTitle = incidentsReportHeaderTitles[header]; @@ -143,6 +214,10 @@ class SingleIncidentsTable extends Component { Cell: props => { let cellValue = ''; let urlValue = undefined; + let clickablePrice = undefined; + let priceAsNumber = undefined; + let priceInputID = undefined; + let priceFontColor = 'black'; switch (props.column.id) { case 'memberName': @@ -198,9 +273,44 @@ class SingleIncidentsTable extends Component { } break; case 'totalChargeFee': - const totalFee = (props.row['_original'].incidentPrice || props.value) || 0; - const totalFeeFormatted = parseFloat(totalFee).toFixed(2); - cellValue = `$ ${totalFeeFormatted}`; + clickablePrice = true; + let totalFee = 0; + + let changedIncidentsProxy; + switch (props.row['_original'].incidentType) { + case UNLOCKED_INCIDENT_STANDALONE: + case UNLOCKED_INCIDENT_RELATED_WITH_RESERVATION: + changedIncidentsProxy = changedUnlockedIncidentIds; + break; + case UNSCHEDULED_INCIDENT_STANDALONE: + case UNSCHEDULED_INCIDENT_BEFORE_RESERVATION: + case UNSCHEDULED_INCIDENT_AFTER_RESERVATION: + changedIncidentsProxy = changedUnscheduledIncidentIds; + break; + case BOOKING_CANCELED_LATE: + case BOOKING_MOVED_TO_ANOTHER_DAY: + case BOOKING_SHORTENED: + changedIncidentsProxy = changedBookingChangeIncidentIds; + break; + } + const changedFee = changedIncidentsProxy[props.row['_original'].incidentId]; + + if (typeof changedFee === 'number'){ + // Not undefined, maybe 0 or NaN + if (isNaN(changedFee)){ + totalFee = ''; + }else{ + totalFee = changedFee; + priceFontColor = 'red'; + } + }else{ + //Not defined, in this context means it is not changed + totalFee = parseFloat((props.row['_original'].incidentPrice || props.value) || 0).toFixed(2); + } + // const totalFeeFormatted = parseFloat(totalFee).toFixed(2); + priceAsNumber = totalFee; + priceInputID = `${props.row['_original'].incidentType || ''}-${props.row['_original'].incidentId || ''}`; + // cellValue = `$ ${totalFeeFormatted}`; columnContentsAlignment = columnAlignments.right; break; case 'oldReservation': @@ -217,10 +327,22 @@ class SingleIncidentsTable extends Component { cellValue = props.value; } - if (openMemberSummaryOnMemberClick && urlValue){ - return {cellValue} + if (clickablePrice){ + return

$ +

}else{ - return
{cellValue}
+ if (openMemberSummaryOnMemberClick && urlValue){ + return {cellValue} + }else{ + return
{cellValue}
+ } } } }); @@ -235,6 +357,9 @@ class SingleIncidentsTable extends Component { { } + { + + }

{ !loading && incidents && @@ -252,7 +377,8 @@ class SingleIncidentsTable extends Component { } const mapDispatchToProps = (dispatch) => ({ - deleteIncidentsById: (dateRange, incidentsToDelete, memberId) => deleteIncidents(dispatch, {dateRange, incidentsToDelete, memberId}) + deleteIncidentsById: (dateRange, incidentsToDelete, memberId) => deleteIncidents(dispatch, {dateRange, incidentsToDelete, memberId}), + updateIncidentsById: (dateRange, updatedIncidentsData, memberId) => updateIncidentFees(dispatch, {dateRange, updatedIncidentsData, memberId}) }); export default connect(null, mapDispatchToProps)(SingleIncidentsTable); diff --git a/client/src/store/actions/integrationActions.js b/client/src/store/actions/integrationActions.js index f285e29..f5320cb 100644 --- a/client/src/store/actions/integrationActions.js +++ b/client/src/store/actions/integrationActions.js @@ -103,6 +103,21 @@ export const deleteIncidents = (dispatch, deleteData) => { }); }; +export const updateIncidentFees = (dispatch, updateData) => { + const pendingAction = updateData.memberId ? FETCH_MEMBER_INCIDENTS_PENDING : FETCH_INCIDENTS_PENDING; + const successAction = updateData.memberId ? FETCH_MEMBER_INCIDENTS_SUCCESS : FETCH_INCIDENTS_SUCCESS; + const failedAction = updateData.memberId ? FETCH_MEMBER_INCIDENTS_FAILED : FETCH_INCIDENTS_FAILED; + + dispatch({type: pendingAction}); + API.patch(`/integration/fees`, updateData) + .then(response => { + dispatch({type: successAction, payload: response.data}); + }) + .catch(error => { + dispatch({type: failedAction, payload: error.response}); + }); +}; + export const fetchMembersList = (dispatch) => { dispatch({type: FETCH_MEMBERS_PENDING}); API.get('officeRnD/membersList') diff --git a/controllers/integration.js b/controllers/integration.js index e9c2d8b..83c7016 100644 --- a/controllers/integration.js +++ b/controllers/integration.js @@ -2,15 +2,25 @@ const moment = require('moment-timezone'); -const { getMappingsFromDatabase, fetchOffices, fetchResources, saveNewMappingToDatabase, deleteMappingById, updateMappingById } = require('../services/officeRnD/resources'); +const { + getMappingsFromDatabase, + fetchOffices, + fetchResources, + saveNewMappingToDatabase, + deleteMappingById, + updateMappingById } = require('../services/officeRnD/resources'); const { getAllIncidents, getMemberPracticeSummaryReport } = require('../services/integration/reports'); const { getMembersFeesForDateRange } = require('../services/integration/invoiceIntegration'); const { deleteFeesFromORD, addFeesToORD } = require('../services/officeRnD/fees'); const { reformatMembershipsName } = require('../services/officeRnD/memberships'); const { checkBookingChanges } = require('../services/integration/checkBookingChange'); const { checkIfProcessing } = require('../services/integration/processingStatus'); -const { deleteUnlockedIncidentsById, deleteUnscheduledIncidentsById } = require('../services/integration/doorLockCharges'); -const { deleteBookingChangeIncidentsById } = require('../services/integration/bookingChangeCharges'); +const { + deleteUnlockedIncidentsById, + deleteUnscheduledIncidentsById, + updateUnscheduledIncidentsById, + updateUnlockedIncidentsById } = require('../services/integration/doorLockCharges'); +const { deleteBookingChangeIncidentsById, updateBookingChangeIncidentsById } = require('../services/integration/bookingChangeCharges'); const { UI_TIMEZONE, DEFAULT_DATE_FORMAT, ALLOW_SENDING_FEES, integrationServiceErrors } = require('../constants/constants'); @@ -177,7 +187,7 @@ const addFees = (req, res) => { } }; -const deleteFees= (req, res) => { +const deleteFees = (req, res) => { const deleteData = req.body; const dateRange = deleteData.dateRange ? deleteData.dateRange : null; const incidents = deleteData.incidentsToDelete ? deleteData.incidentsToDelete : null; @@ -220,6 +230,49 @@ const deleteFees= (req, res) => { } }; +const updateFees = (req, res) => { + const updateData = req.body; + const dateRange = updateData.dateRange ? updateData.dateRange : null; + const incidents = updateData.updatedIncidentsData ? updateData.updatedIncidentsData : null; + const memberId = updateData.memberId ? updateData.memberId : null; + + const unlockedIncidentFees = incidents.unlockedIncidentFees ? incidents.unlockedIncidentFees : {}; + const unscheduledIncidentFees = incidents.unscheduledIncidentFees ? incidents.unscheduledIncidentFees : {}; + const bookingChangeIncidentFees = incidents.bookingChangeIncidentFees ? incidents.bookingChangeIncidentFees : {}; + + req.params.startDate = dateRange.startDate ? dateRange.startDate : null; + req.params.endDate = dateRange.endDate ? dateRange.endDate : null; + + if (unlockedIncidentFees && unscheduledIncidentFees && bookingChangeIncidentFees){ + const asyncUpdateActions = [ + updateUnlockedIncidentsById(unlockedIncidentFees), + updateUnscheduledIncidentsById(unscheduledIncidentFees), + updateBookingChangeIncidentsById(bookingChangeIncidentFees) + ]; + + Promise.all(asyncUpdateActions) + .then(() => { + if (memberId){ + req.params.memberId = memberId; + getMemberIncidents(req, res); + }else{ + getAllIncidentsController(req, res); + } + }) + .catch((error) => { + console.log('Error updating incidents : ', error); + res.status(500).send(); + }); + }else{ + if (memberId){ + req.params.memberId = memberId; + getMemberIncidents(req, res); + }else{ + getAllIncidentsController(req, res); + } + } +}; + const checkProcessingStatus = (req, res) => { checkIfProcessing() .then((processing) => { @@ -272,5 +325,6 @@ module.exports = { addFees, checkProcessingStatus, getPracticeSummaryReport, - deleteFees + deleteFees, + updateFees }; diff --git a/routes/index.js b/routes/index.js index e35c18d..699bcde 100644 --- a/routes/index.js +++ b/routes/index.js @@ -13,7 +13,8 @@ const { addFees, checkProcessingStatus, getPracticeSummaryReport, - deleteFees + deleteFees, + updateFees } = require('../controllers/integration'); const { calculateDoorLockCharges } = require('../services/integration/doorLockCharges'); @@ -36,6 +37,7 @@ router.get('/officeRnD/membersList', fetchMembersList); router.post('/integration/addFees', addFees); router.delete('/integration/fees', deleteFees); +router.patch('/integration/fees', updateFees); router.get('/integration/processing', checkProcessingStatus); @@ -44,6 +46,6 @@ router.get('/integration/report/practiceSummary/:year', getPracticeSummaryReport // temporary route, manually trigger door lock charge calculations -router.get('/calculate', (req, res) => { calculateDoorLockCharges(); res.send();}); +router.get('/calculate', (req, res) => { calculateDoorLockCharges().then(() => res.send('Done')).catch((err) => res.send('Error \r\n', err));}); module.exports = router; diff --git a/services/integration/bookingChangeCharges.js b/services/integration/bookingChangeCharges.js index 1f6f2ba..93e5361 100644 --- a/services/integration/bookingChangeCharges.js +++ b/services/integration/bookingChangeCharges.js @@ -283,6 +283,22 @@ const deleteBookingChangeIncidentsById = (incidentIds) => { return db.bookingChangeIncident.update({deleted: true},{where: filters}); }; +const updateBookingChangeIncidentsById = (incidentFees) => { + const incidentIds = Object.keys(incidentFees); + + const asyncUpdateActions = []; + + incidentIds.forEach((incidentId) => { + const newFeeCharge = parseFloat(incidentFees[incidentId]); + + if (newFeeCharge || (newFeeCharge === 0)){ + asyncUpdateActions.push(db.bookingChangeIncident.update({chargeFee: newFeeCharge}, {where: {id: incidentId}})); + } + }); + + return Promise.all(asyncUpdateActions); +}; + module.exports = { getChargedCanceledReservations, getIncidentsFromChanges, @@ -290,5 +306,6 @@ module.exports = { getShorteningIncidentsForReservationId, getReservationsIncidentsForRemoval, deleteBookingChangeIncidents, - deleteBookingChangeIncidentsById + deleteBookingChangeIncidentsById, + updateBookingChangeIncidentsById }; diff --git a/services/integration/doorLockCharges.js b/services/integration/doorLockCharges.js index d331a37..b18d95c 100644 --- a/services/integration/doorLockCharges.js +++ b/services/integration/doorLockCharges.js @@ -80,6 +80,26 @@ const deleteUnscheduledIncidentsById = (incidentIds) => { return db.unscheduledIncident.update({deleted: true},{where: filters}); }; +const updateUnscheduledIncidentsById = (incidentFees) => { + const incidentIds = Object.keys(incidentFees); + + const asyncUpdateActions = []; + + incidentIds.forEach((incidentId) => { + const newFeeCharge = parseFloat(incidentFees[incidentId]); + + if (newFeeCharge || (newFeeCharge === 0)){ + asyncUpdateActions.push(db.unscheduledIncident.update({ + totalChargeFee: newFeeCharge, + chargePrice: newFeeCharge, + timeIntervalsToCharge: 1 + }, {where: {id: incidentId}})); + } + }); + + return Promise.all(asyncUpdateActions); +}; + const insertUnlockedIncidents = (incidents) => { const asyncJobs = []; incidents.forEach((incident) => { @@ -124,6 +144,22 @@ const deleteUnlockedIncidentsById = (incidentIds) => { return db.unlockedIncident.update({deleted: true},{where: filters}); }; +const updateUnlockedIncidentsById = (incidentFees) => { + const incidentIds = Object.keys(incidentFees); + + const asyncUpdateActions = []; + + incidentIds.forEach((incidentId) => { + const newFeeCharge = parseFloat(incidentFees[incidentId]); + + if (newFeeCharge || (newFeeCharge === 0)){ + asyncUpdateActions.push(db.unlockedIncident.update({incidentLevelPrice: newFeeCharge}, {where: {id: incidentId}})); + } + }); + + return Promise.all(asyncUpdateActions); +}; + const setUnlockedIncidentsLevel = (incidents) => { return new Promise ((resolve, reject) => { const sortingFunction = (incidentA, incidentB) => { @@ -850,5 +886,7 @@ const calculateDoorLockCharges = () => { module.exports = { calculateDoorLockCharges, deleteUnlockedIncidentsById, - deleteUnscheduledIncidentsById + deleteUnscheduledIncidentsById, + updateUnlockedIncidentsById, + updateUnscheduledIncidentsById };