Merge branch 'allow-incident-price-modification-frontend' into 'master'

Allow incident price modification frontend+backend

See merge request saburly/psihologija!81
This commit was merged in pull request #81.
This commit is contained in:
Bilal Catic
2020-01-09 17:13:04 +00:00
6 changed files with 272 additions and 20 deletions

View File

@@ -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 <NavLink to={urlValue}>{cellValue}</NavLink>
if (clickablePrice){
return <p>$ <input
id={priceInputID}
style={{ color: priceFontColor }}
type={'number'}
onChange={priceChangeHandler}
value={priceAsNumber}
autoFocus={priceInputID === inputIdToFocus}
/>
</p>
}else{
return <div style={{ textAlign: columnContentsAlignment, whiteSpace: 'pre' }}>{cellValue}</div>
if (openMemberSummaryOnMemberClick && urlValue){
return <NavLink to={urlValue}>{cellValue}</NavLink>
}else{
return <div style={{ textAlign: columnContentsAlignment, whiteSpace: 'pre' }}>{cellValue}</div>
}
}
}
});
@@ -235,6 +357,9 @@ class SingleIncidentsTable extends Component {
{
<Button disabled={loading || totalSelected === 0} onClick={this.deleteSelectedFees}>{`Delete selected ${numberOfSelectedText}`}</Button>
}
{
<Button disabled={loading || totalChanged === 0} onClick={this.updateChangedFees}>{`Save changed ${numberOfChangedText}`}</Button>
}
<br/><br/>
{
!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);

View File

@@ -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')

View File

@@ -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
};

View File

@@ -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;

View File

@@ -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
};

View File

@@ -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
};