Charges/invoices update

This commit is contained in:
Senad Uka
2019-07-19 09:46:15 +02:00
parent e2dcb246f0
commit 4389aaa8da
16 changed files with 253 additions and 62 deletions

View File

@@ -81,7 +81,7 @@ class DateRangePicker extends Component {
const endDateValue = endDate.format(defaultDateFormat); const endDateValue = endDate.format(defaultDateFormat);
const buttonRender = ( const buttonRender = (
<Grid.Column width={inlineButton ? 1 : null}> <Grid.Column width={inlineButton ? 3 : null}>
{ inlineButton && <label>{'\u00A0'}</label> } { inlineButton && <label>{'\u00A0'}</label> }
<Form.Button onClick={this.onButtonClick.bind(this)}>{buttonLabel}</Form.Button> <Form.Button onClick={this.onButtonClick.bind(this)}>{buttonLabel}</Form.Button>
</Grid.Column> </Grid.Column>

View File

@@ -70,6 +70,19 @@ const SingleIncidentsTable = props => {
urlValue = `/practice-summary-report/${memberId}`; urlValue = `/practice-summary-report/${memberId}`;
cellValue = props.value; cellValue = props.value;
break; break;
case 'resourceName':
if (props.row['_original'].resourceName){
cellValue = props.row['_original'].resourceName || '---';
}else{
const oldResourceName = props.row['_original'].oldResourceName || '---';
const newResourceName = props.row['_original'].newResourceName || '---';
if (oldResourceName !== newResourceName){
cellValue = `${oldResourceName}\n${newResourceName}`;
}else{
cellValue = oldResourceName;
}
}
break;
case 'reservation': case 'reservation':
const bookingStart = props.row['_original'].bookingStart; const bookingStart = props.row['_original'].bookingStart;
const bookingEnd = props.row['_original'].bookingEnd; const bookingEnd = props.row['_original'].bookingEnd;
@@ -116,8 +129,8 @@ const SingleIncidentsTable = props => {
cellValue = `${oldBookingStart}\n${oldBookingEnd}`; cellValue = `${oldBookingStart}\n${oldBookingEnd}`;
break; break;
case 'newReservation': case 'newReservation':
const newBookingStart = props.row['_original'].newBookingStart; const newBookingStart = props.row['_original'].newBookingStart || '---';
const newBookingEnd = props.row['_original'].newBookingEnd; const newBookingEnd = props.row['_original'].newBookingEnd || '---';
cellValue = `${newBookingStart}\n${newBookingEnd}`; cellValue = `${newBookingStart}\n${newBookingEnd}`;
break; break;
default: default:

View File

@@ -3,7 +3,7 @@ import {Accordion, Label} from 'semantic-ui-react';
import SingleIncidentsTable from './components/SingleIncidentsTable'; import SingleIncidentsTable from './components/SingleIncidentsTable';
import { import {
UNLOCKED_INCIDENT_RELATED_WITH_RESERVATION, UNLOCKED_INCIDENT_STANDALONE, UNSCHEDULED_INCIDENT_AFTER_RESERVATION, UNLOCKED_INCIDENT_RELATED_WITH_RESERVATION, UNLOCKED_INCIDENT_STANDALONE, UNSCHEDULED_INCIDENT_AFTER_RESERVATION,
UNSCHEDULED_INCIDENT_BEFORE_RESERVATION, UNSCHEDULED_INCIDENT_STANDALONE, BOOKING_MOVED_TO_ANOTHER_DAY, BOOKING_SHORTENED, UNSCHEDULED_INCIDENT_BEFORE_RESERVATION, UNSCHEDULED_INCIDENT_STANDALONE, BOOKING_MOVED_TO_ANOTHER_DAY, BOOKING_SHORTENED, BOOKING_CANCELED_LATE,
incidentTableTypes incidentTableTypes
} from '../../constants/enums'; } from '../../constants/enums';
@@ -29,6 +29,7 @@ export default function MemberIncidentsTables (props) {
break; break;
case BOOKING_MOVED_TO_ANOTHER_DAY: case BOOKING_MOVED_TO_ANOTHER_DAY:
case BOOKING_SHORTENED: case BOOKING_SHORTENED:
case BOOKING_CANCELED_LATE:
bookingChangeIncidents.push(incident); bookingChangeIncidents.push(incident);
break; break;
default: default:

View File

@@ -6,6 +6,7 @@ export const UNLOCKED_INCIDENT_STANDALONE = 5;
export const UNSCHEDULED_INCIDENT_STANDALONE = 6; export const UNSCHEDULED_INCIDENT_STANDALONE = 6;
export const BOOKING_MOVED_TO_ANOTHER_DAY = 7; export const BOOKING_MOVED_TO_ANOTHER_DAY = 7;
export const BOOKING_SHORTENED = 8; export const BOOKING_SHORTENED = 8;
export const BOOKING_CANCELED_LATE = 9;
export const incidentDescriptions = {}; export const incidentDescriptions = {};
incidentDescriptions[UNLOCKED_INCIDENT_RELATED_WITH_RESERVATION] = 'User left door unlocked'; incidentDescriptions[UNLOCKED_INCIDENT_RELATED_WITH_RESERVATION] = 'User left door unlocked';
@@ -15,6 +16,7 @@ incidentDescriptions[UNLOCKED_INCIDENT_STANDALONE] = 'User left door unlocked';
incidentDescriptions[UNSCHEDULED_INCIDENT_STANDALONE] = 'Unscheduled use'; incidentDescriptions[UNSCHEDULED_INCIDENT_STANDALONE] = 'Unscheduled use';
incidentDescriptions[BOOKING_MOVED_TO_ANOTHER_DAY] = 'Reservation moved to another day'; incidentDescriptions[BOOKING_MOVED_TO_ANOTHER_DAY] = 'Reservation moved to another day';
incidentDescriptions[BOOKING_SHORTENED] = 'Reservation shortened'; incidentDescriptions[BOOKING_SHORTENED] = 'Reservation shortened';
incidentDescriptions[BOOKING_CANCELED_LATE] = 'Reservation canceled late';
export const incidentLevelDescriptions = { export const incidentLevelDescriptions = {
UNLOCKED_0: 'First month', UNLOCKED_0: 'First month',

View File

@@ -5,7 +5,7 @@ import {
UNSCHEDULED_INCIDENT_BEFORE_RESERVATION, UNSCHEDULED_INCIDENT_BEFORE_RESERVATION,
UNLOCKED_INCIDENT_RELATED_WITH_RESERVATION, UNLOCKED_INCIDENT_RELATED_WITH_RESERVATION,
UNSCHEDULED_INCIDENT_AFTER_RESERVATION, UNSCHEDULED_INCIDENT_STANDALONE, UNLOCKED_INCIDENT_STANDALONE, UNSCHEDULED_INCIDENT_AFTER_RESERVATION, UNSCHEDULED_INCIDENT_STANDALONE, UNLOCKED_INCIDENT_STANDALONE,
BOOKING_MOVED_TO_ANOTHER_DAY, BOOKING_SHORTENED, BOOKING_MOVED_TO_ANOTHER_DAY, BOOKING_SHORTENED, BOOKING_CANCELED_LATE,
} from '../../../constants/enums'; } from '../../../constants/enums';
const MemberSummary = props => { const MemberSummary = props => {
@@ -29,6 +29,7 @@ const MemberSummary = props => {
break; break;
case BOOKING_MOVED_TO_ANOTHER_DAY: case BOOKING_MOVED_TO_ANOTHER_DAY:
case BOOKING_SHORTENED: case BOOKING_SHORTENED:
case BOOKING_CANCELED_LATE:
totalBookingChangeFees += parseFloat(incident.totalChargeFee); totalBookingChangeFees += parseFloat(incident.totalChargeFee);
break; break;
default: default:

View File

@@ -68,6 +68,7 @@ const incidentType = {
UNSCHEDULED_INCIDENT_STANDALONE: 6, UNSCHEDULED_INCIDENT_STANDALONE: 6,
BOOKING_MOVED_TO_ANOTHER_DAY: 7, BOOKING_MOVED_TO_ANOTHER_DAY: 7,
BOOKING_SHORTENED: 8, BOOKING_SHORTENED: 8,
BOOKING_CANCELED_LATE: 9,
}; };
const UI_TIMEZONE = process.env.UI_TIMEZONE || 'America/Los_Angeles'; const UI_TIMEZONE = process.env.UI_TIMEZONE || 'America/Los_Angeles';

View File

@@ -4,18 +4,26 @@ require('dotenv').config();
const { fetchAllBookings, bulkWriteReservationsWithChangesTracking } = require('../services/officeRnD/bookings'); const { fetchAllBookings, bulkWriteReservationsWithChangesTracking } = require('../services/officeRnD/bookings');
const { chargeBookingChanges } = require('../services/integration/bookingChangeCharges'); const { chargeBookingChanges } = require('../services/integration/bookingChangeCharges');
const { bulkWriteChanges } = require('../services/integration/bookingChangeLog');
const checkBookingChanges = () => { const checkBookingChanges = () => {
fetchAllBookings() fetchAllBookings()
.then((reservations) => { .then((reservations) => {
bulkWriteReservationsWithChangesTracking(reservations) bulkWriteReservationsWithChangesTracking(reservations)
.then((changes) => { .then((changes) => {
chargeBookingChanges(changes) bulkWriteChanges(changes)
.then(() => { .then(() => {
process.exit(); chargeBookingChanges(changes)
.then(() => {
process.exit();
})
.catch((error) => {
console.log('Error creating charges ', error);
process.exit();
});
}) })
.catch((error) => { .catch((error) => {
console.log('Error creating charges ', error); console.log('Error bulk write booking reservation change log :', error);
process.exit(); process.exit();
}); });
}) })

View File

@@ -0,0 +1,11 @@
'use strict';
module.exports = {
up: (queryInterface, Sequelize) => {
return queryInterface.renameColumn('bookingChangeIncidents', 'resourceId', 'oldResourceId');
},
down: (queryInterface, Sequelize) => {
return queryInterface.renameColumn('bookingChangeIncidents', 'oldResourceId', 'resourceId');
}
};

View File

@@ -0,0 +1,14 @@
'use strict';
module.exports = {
up: (queryInterface, Sequelize) => {
return queryInterface.addColumn('bookingChangeIncidents', 'newResourceId', {
type: Sequelize.TEXT,
after: 'oldResourceId',
});
},
down: (queryInterface, Sequelize) => {
return queryInterface.removeColumn('bookingChangeIncidents', 'newResourceId');
}
};

View File

@@ -0,0 +1,35 @@
'use strict';
module.exports = {
up: (queryInterface, Sequelize) => {
return queryInterface.createTable('bookingReservationChangeLogs', {
id: {
allowNull: false,
autoIncrement: true,
primaryKey: true,
type: Sequelize.INTEGER
},
reservationId: Sequelize.TEXT,
memberId: Sequelize.TEXT,
officeId: Sequelize.TEXT,
oldResourceId: Sequelize.TEXT,
newResourceId: Sequelize.TEXT,
oldStart: Sequelize.DATE,
oldEnd: Sequelize.DATE,
newStart: Sequelize.DATE,
newEnd: Sequelize.DATE,
canceled: Sequelize.BOOLEAN,
createdAt: {
allowNull: false,
type: Sequelize.DATE
},
updatedAt: {
allowNull: false,
type: Sequelize.DATE
}
});
},
down: (queryInterface, Sequelize) => {
return queryInterface.dropTable('bookingReservationChangeLogs');
}
};

View File

@@ -4,7 +4,8 @@ module.exports = (sequelize, DataTypes) => {
const bookingChangeIncident = sequelize.define('bookingChangeIncident', { const bookingChangeIncident = sequelize.define('bookingChangeIncident', {
reservationId: DataTypes.TEXT, reservationId: DataTypes.TEXT,
memberId: DataTypes.TEXT, memberId: DataTypes.TEXT,
resourceId: DataTypes.TEXT, oldResourceId: DataTypes.TEXT,
newResourceId: DataTypes.TEXT,
oldBookingStart: DataTypes.DATE, oldBookingStart: DataTypes.DATE,
oldBookingEnd: DataTypes.DATE, oldBookingEnd: DataTypes.DATE,
newBookingStart: DataTypes.DATE, newBookingStart: DataTypes.DATE,

View File

@@ -0,0 +1,20 @@
'use strict';
module.exports = (sequelize, DataTypes) => {
const bookingReservationChangeLog = sequelize.define('bookingReservationChangeLog', {
reservationId: DataTypes.TEXT,
memberId: DataTypes.TEXT,
officeId: DataTypes.TEXT,
oldResourceId: DataTypes.TEXT,
newResourceId: DataTypes.TEXT,
oldStart: DataTypes.DATE,
oldEnd: DataTypes.DATE,
newStart: DataTypes.DATE,
newEnd: DataTypes.DATE,
canceled: DataTypes.BOOLEAN,
}, {});
bookingReservationChangeLog.associate = function(models) {
// associations can be defined here
};
return bookingReservationChangeLog;
};

View File

@@ -24,10 +24,10 @@ const chargeBookingChanges = (changes) => {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
if (Array.isArray(changes)){ if (Array.isArray(changes)){
const incidents = []; const incidents = [];
const errors = [];
changes.forEach((change) => { changes.forEach((change) => {
const { oldReservation, newReservation } = change; const { oldReservation, newReservation } = change;
if (oldReservation && newReservation){ if (oldReservation && newReservation){
const oldResourceId = oldReservation.resourceId;
const oldStart = oldReservation.start ? moment.utc(oldReservation.start) : null; const oldStart = oldReservation.start ? moment.utc(oldReservation.start) : null;
const oldEnd = oldReservation.end ? moment.utc(oldReservation.end) : null; const oldEnd = oldReservation.end ? moment.utc(oldReservation.end) : null;
@@ -35,7 +35,8 @@ const chargeBookingChanges = (changes) => {
const newEnd = newReservation.end ? moment.utc(newReservation.end) : null; const newEnd = newReservation.end ? moment.utc(newReservation.end) : null;
const reservationTimezone = newReservation.timezone ? newReservation.timezone : UI_TIMEZONE; const reservationTimezone = newReservation.timezone ? newReservation.timezone : UI_TIMEZONE;
const reservationHourlyRate = newReservation.hourlyRate ? newReservation.hourlyRate : undefined; const reservationHourlyRate = oldReservation.hourlyRate ? oldReservation.hourlyRate : undefined;
const canceled = newReservation.canceled;
if (oldStart && oldEnd && newStart && newEnd && reservationHourlyRate){ if (oldStart && oldEnd && newStart && newEnd && reservationHourlyRate){
const oldReservationLength = oldEnd.diff(oldStart, 'hours', true); const oldReservationLength = oldEnd.diff(oldStart, 'hours', true);
@@ -43,66 +44,78 @@ const chargeBookingChanges = (changes) => {
const differenceFromNow = oldStart.diff(moment.utc(), 'hours'); const differenceFromNow = oldStart.diff(moment.utc(), 'hours');
if (differenceFromNow && (differenceFromNow < 24)){ if (differenceFromNow < 24){
// Changed reservation that was within 24hrs from now // Changed reservation that was within 24hrs from now
const { reservationId, memberId, resourceId } = newReservation;
// Check if new reservation is on same day if (!canceled) {
const sameDay = oldStart.tz(reservationTimezone).isSame(newStart.tz(reservationTimezone), 'day'); // Check if new reservation is on same day
const sameDay = oldStart.tz(reservationTimezone).isSame(newStart.tz(reservationTimezone), 'day');
const { reservationId, memberId, resourceId, hourlyRate } = newReservation; if (sameDay) {
// Reservation moved in same day
// Check if member shortened the reservation
if (sameDay){ if (newReservationLength < oldReservationLength) {
// Reservation moved in same day const differenceInLength = oldReservationLength - newReservationLength;
// Check if member shortened the reservation const chargeFee = differenceInLength * reservationHourlyRate * BOOKING_CHANGE_PERCENTAGE_CHARGE / 100;
if (newReservationLength < oldReservationLength){ const incident = {
reservationId,
memberId,
oldResourceId: oldResourceId || resourceId,
newResourceId: resourceId,
oldBookingStart: oldReservation.start,
oldBookingEnd: oldReservation.end,
newBookingStart: newReservation.start,
newBookingEnd: newReservation.end,
incidentType: incidentType.BOOKING_SHORTENED,
chargeFee,
};
const differenceInLength = oldReservationLength - newReservationLength; incidents.push(incident);
const chargeFee = differenceInLength*hourlyRate*BOOKING_CHANGE_PERCENTAGE_CHARGE/100; }
} else {
// Reservation moved to another day
// Add cancellation charge
const chargeFee = oldReservationLength * reservationHourlyRate * BOOKING_CHANGE_PERCENTAGE_CHARGE / 100;
const incident = { const incident = {
reservationId, reservationId,
memberId, memberId,
resourceId, oldResourceId: oldResourceId || resourceId,
newResourceId: resourceId,
oldBookingStart: oldReservation.start, oldBookingStart: oldReservation.start,
oldBookingEnd: oldReservation.end, oldBookingEnd: oldReservation.end,
newBookingStart: newReservation.start, newBookingStart: newReservation.start,
newBookingEnd: newReservation.end, newBookingEnd: newReservation.end,
incidentType: incidentType.BOOKING_SHORTENED, incidentType: incidentType.BOOKING_MOVED_TO_ANOTHER_DAY,
chargeFee, chargeFee,
}; };
incidents.push(incident); incidents.push(incident);
} }
}else{ }else{
// Reservation moved to another day const chargeFee = 2 * reservationHourlyRate * oldReservationLength * BOOKING_CHANGE_PERCENTAGE_CHARGE / 100;
// Add cancellation charge
const chargeFee = oldReservationLength*hourlyRate*BOOKING_CHANGE_PERCENTAGE_CHARGE/100;
const incident = { const incident = {
reservationId, reservationId,
memberId, memberId,
resourceId, oldResourceId: oldResourceId || resourceId,
newResourceId: null,
oldBookingStart: oldReservation.start, oldBookingStart: oldReservation.start,
oldBookingEnd: oldReservation.end, oldBookingEnd: oldReservation.end,
newBookingStart: newReservation.start, newBookingStart: null,
newBookingEnd: newReservation.end, newBookingEnd: null,
incidentType: incidentType.BOOKING_MOVED_TO_ANOTHER_DAY, incidentType: incidentType.BOOKING_CANCELED_LATE,
chargeFee, chargeFee,
}; };
incidents.push(incident); incidents.push(incident);
} }
} }
}else{
errors.push(change);
} }
} }
}); });
if (errors.length > 0){
console.log('There were some errors with incomplete bookings : ');
console.log(errors);
}
resolve(bulkWriteBookingChangeIncidents(incidents)); resolve(bulkWriteBookingChangeIncidents(incidents));
}else{ }else{
reject('Input argument is not an array !'); reject('Input argument is not an array !');

View File

@@ -0,0 +1,48 @@
'use strict';
const db = require('../../models/index');
const bulkWriteChanges = ((changes) => {
const changeLogsForDB = [];
changes.forEach((change) => {
const { oldReservation, newReservation } = change;
const { reservationId, memberId, officeId, resourceId, start, end, canceled } = newReservation;
const logEntry = {
reservationId: oldReservation.reservationId || reservationId,
memberId: oldReservation.memberId || memberId,
officeId: oldReservation.officeId || officeId,
oldResourceId: oldReservation.resourceId || resourceId,
newResourceId: resourceId,
oldStart: oldReservation.start || start,
newStart: start,
oldEnd: oldReservation.end || end,
newEnd: end,
canceled,
};
if (!oldReservation.start && !oldReservation.end && !oldReservation.resourceId){
// new reservation
logEntry.oldResourceId = null;
logEntry.oldStart = null;
logEntry.oldEnd = null;
}
if (newReservation.canceled){
logEntry.newResourceId = null;
logEntry.newStart = null;
logEntry.newEnd = null;
}
changeLogsForDB.push(logEntry);
});
return db.bookingReservationChangeLog.bulkCreate(changeLogsForDB);
// console.log(changeLogsForDB);
// return new Promise((resolve) => resolve());
});
module.exports = {
bulkWriteChanges,
};

View File

@@ -116,7 +116,8 @@ const getBookingChangeIncidents = (startDate, endDate, memberId) => {
'id', 'id',
'reservationId', 'reservationId',
'memberId', 'memberId',
'resourceId', 'oldResourceId',
'newResourceId',
'oldBookingStart', 'oldBookingStart',
'oldBookingEnd', 'oldBookingEnd',
'newBookingStart', 'newBookingStart',
@@ -251,7 +252,8 @@ const getAllIncidents = (dateRange, memberId) => {
const { const {
id, id,
memberId, memberId,
resourceId, oldResourceId,
newResourceId,
oldBookingStart, oldBookingStart,
oldBookingEnd, oldBookingEnd,
newBookingStart, newBookingStart,
@@ -261,14 +263,17 @@ const getAllIncidents = (dateRange, memberId) => {
createdAt, createdAt,
} = bookingChangeIncident; } = bookingChangeIncident;
const memberName = membersMap[memberId].name; const memberName = membersMap[memberId].name;
const resource = resourcesMap[resourceId]; const oldResource = resourcesMap[oldResourceId];
const resourceName = resource.resourceName; const newResource = newResourceId ? resourcesMap[newResourceId] : null;
const officeName = officesMap[resource.officeId].officeName; const oldResourceName = oldResource.resourceName;
const newResourceName = newResource ? newResource.resourceName : null;
const officeName = officesMap[oldResource.officeId].officeName;
allIncidents.push({ allIncidents.push({
incidentId: id, incidentId: id,
memberId, memberId,
memberName, memberName,
resourceName, oldResourceName,
newResourceName,
officeName, officeName,
oldBookingStart: formatTime(oldBookingStart), oldBookingStart: formatTime(oldBookingStart),
oldBookingEnd: formatTime(oldBookingEnd), oldBookingEnd: formatTime(oldBookingEnd),

View File

@@ -174,7 +174,7 @@ const bulkWriteReservationsWithChangesTracking = (reservations) => {
const changedKeys = instance.changed(); const changedKeys = instance.changed();
const previous = instance.previous(); const previous = instance.previous();
const lookupKeys = ['start', 'end']; const lookupKeys = ['start', 'end', 'resourceId', 'canceled'];
let realChange = false; let realChange = false;
lookupKeys.forEach((key) => { lookupKeys.forEach((key) => {
@@ -184,6 +184,8 @@ const bulkWriteReservationsWithChangesTracking = (reservations) => {
} }
}); });
instance.setDataValue('hourlyRate', previous.hourlyRate);
if (realChange){ if (realChange){
changes.push({ changes.push({
oldReservation: previous, oldReservation: previous,
@@ -193,25 +195,41 @@ const bulkWriteReservationsWithChangesTracking = (reservations) => {
}); });
reservations.forEach((reservation) => { reservations.forEach((reservation) => {
asyncJobs.push( const asyncReservationUpdate = () => {
db.bookingReservation.update(reservation, { return new Promise((resolve, reject) => {
where: { db.bookingReservation.update(reservation, {
reservationId: reservation.reservationId, where: {
}, reservationId: reservation.reservationId,
returning: true, },
individualHooks: true, returning: true,
}) individualHooks: true,
.then(([updateCount, updatedInstances]) => {
if (updateCount === 0){
db.bookingReservation.upsert(reservation);
}
}) })
.catch((error) => { .then(([updateCount, updatedInstances]) => {
console.log('Error updating'); if (updateCount === 0){
console.log(error); const oldReservation = {
reject(error); start: null,
}) end: null,
); resourceId: null,
};
changes.push({
oldReservation,
newReservation: reservation,
});
resolve(db.bookingReservation.upsert(reservation));
}else{
resolve();
}
})
.catch((error) => {
console.log('Error updating');
console.log(error);
reject(error);
})
});
};
asyncJobs.push(asyncReservationUpdate());
}); });
Promise.all(asyncJobs) Promise.all(asyncJobs)