create incidents for booking changes

This commit is contained in:
Bilal Catic
2019-07-08 18:23:32 +02:00
parent 7aab1e538e
commit 6bed9aecc0
9 changed files with 230 additions and 23 deletions

View File

@@ -66,6 +66,8 @@ const incidentType = {
UNSCHEDULED_INCIDENT_AFTER_RESERVATION: 4,
UNLOCKED_INCIDENT_STANDALONE: 5,
UNSCHEDULED_INCIDENT_STANDALONE: 6,
BOOKING_MOVED_TO_ANOTHER_DAY: 7,
BOOKING_SHORTENED: 8,
};
const UI_TIMEZONE = process.env.UI_TIMEZONE || 'America/Los_Angeles';
@@ -76,6 +78,8 @@ const MAX_BACK_TO_BACK_DIFFERENCE = parseInt(process.env.MAX_BACK_TO_BACK_DIFFER
const UNSCHEDULED_TIME_RESOLUTION = parseInt(process.env.UNSCHEDULED_USE_TIME_RESOLUTION) || 5;
const UNSCHEDULED_CHARGE_PRICE = parseFloat(process.env.UNSCHEDULED_USE_CHARGE_PRICE) || 5;
const BOOKING_CHANGE_PERCENTAGE_CHARGE = parseInt(process.env.BOOKING_CHANGE_PERCENTAGE_CHARGE) || 100;
module.exports = {
VALID_CSV_HEADERS,
USER_ENTRY_EVENT,
@@ -92,4 +96,5 @@ module.exports = {
MAX_BACK_TO_BACK_DIFFERENCE,
UNSCHEDULED_TIME_RESOLUTION,
UNSCHEDULED_CHARGE_PRICE,
BOOKING_CHANGE_PERCENTAGE_CHARGE,
};

View File

@@ -1,23 +1,32 @@
'use strict';
require('dotenv').config();
const { fetchAllBookings, bulkWriteReservationsWithChangesTracking } = require('../services/officeRnD/bookings');
const { chargeBookingChanges } = require('../services/integration/bookingChangeCharges');
const checkBookingChanges = () => {
fetchAllBookings()
.then((reservations) => {
bulkWriteReservationsWithChangesTracking(reservations)
.then((changes) => {
console.log('== CHANGES == ');
console.log(changes);
chargeBookingChanges(changes)
.then(() => {
process.exit();
})
.catch((error) => {
console.log('Error creating charges ', 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();
});
};

View File

@@ -18,3 +18,5 @@ UNLOCK_4=Price for unlocked door, fifth month
UNLOCK_5=Price for unlocked door, sixth month
UNLOCK_STREAK_REPAIR_AFTER=Number of months without incidents to reset user incident level
BOOKING_CHANGE_PERCENTAGE_CHARGE=Percentage of hourly reate to apply for cancellation-like charges

View File

@@ -0,0 +1,34 @@
'use strict';
module.exports = {
up: (queryInterface, Sequelize) => {
return queryInterface.createTable('bookingChangeIncidents', {
id: {
allowNull: false,
autoIncrement: true,
primaryKey: true,
type: Sequelize.INTEGER
},
reservationId: Sequelize.TEXT,
memberId: Sequelize.TEXT,
resourceId: Sequelize.TEXT,
oldBookingStart: Sequelize.DATE,
oldBookingEnd: Sequelize.DATE,
newBookingStart: Sequelize.DATE,
newBookingEnd: Sequelize.DATE,
incidentType: Sequelize.INTEGER,
chargeFee: Sequelize.FLOAT,
createdAt: {
allowNull: false,
type: Sequelize.DATE
},
updatedAt: {
allowNull: false,
type: Sequelize.DATE
}
});
},
down: (queryInterface, Sequelize) => {
return queryInterface.dropTable('bookingChangeIncidents');
}
};

View File

@@ -0,0 +1,14 @@
'use strict';
module.exports = {
up: (queryInterface, Sequelize) => {
return queryInterface.addColumn('bookingReservations', 'hourlyRate', {
type: Sequelize.FLOAT,
after: 'end',
});
},
down: (queryInterface, Sequelize) => {
return queryInterface.removeColumn('bookingReservations', 'hourlyRate');
}
};

View File

@@ -0,0 +1,19 @@
'use strict';
module.exports = (sequelize, DataTypes) => {
const bookingChangeIncident = sequelize.define('bookingChangeIncident', {
reservationId: DataTypes.TEXT,
memberId: DataTypes.TEXT,
resourceId: DataTypes.TEXT,
oldBookingStart: DataTypes.DATE,
oldBookingEnd: DataTypes.DATE,
newBookingStart: DataTypes.DATE,
newBookingEnd: DataTypes.DATE,
incidentType: DataTypes.INTEGER,
chargeFee: DataTypes.FLOAT,
}, {});
bookingChangeIncident.associate = function(models) {
// associations can be defined here
};
return bookingChangeIncident;
};

View File

@@ -11,6 +11,7 @@ module.exports = (sequelize, DataTypes) => {
resourceId: DataTypes.TEXT,
start: DataTypes.DATE,
end: DataTypes.DATE,
hourlyRate: DataTypes.FLOAT,
timezone: DataTypes.TEXT,
canceled: DataTypes.BOOLEAN,
}, {});

View File

@@ -1,2 +1,115 @@
'use strict';
const moment = require('moment-timezone');
const db = require('../../models/index');
const { UI_TIMEZONE, BOOKING_CHANGE_PERCENTAGE_CHARGE, incidentType } = require('../../constants/constants');
const bulkWriteBookingChangeIncidents = (incidents) => {
return new Promise((resolve, reject) => {
const asyncJobs = [];
incidents.forEach((incident) => {
asyncJobs.push(db.bookingChangeIncident.findOrCreate({where: incident, defaults: incident}));
});
Promise.all(asyncJobs)
.then(() => {
resolve();
})
.catch((error) => reject(error));
});
};
const chargeBookingChanges = (changes) => {
return new Promise((resolve, reject) => {
if (Array.isArray(changes)){
const incidents = [];
const errors = [];
changes.forEach((change) => {
const { oldReservation, newReservation } = change;
if (oldReservation && newReservation){
const oldStart = oldReservation.start ? moment.utc(oldReservation.start) : null;
const oldEnd = oldReservation.end ? moment.utc(oldReservation.end) : null;
const newStart = newReservation.start ? moment.utc(newReservation.start) : null;
const newEnd = newReservation.end ? moment.utc(newReservation.end) : null;
const reservationTimezone = newReservation.timezone ? newReservation.timezone : UI_TIMEZONE;
const reservationHourlyRate = newReservation.hourlyRate ? newReservation.hourlyRate : undefined;
if (oldStart && oldEnd && newStart && newEnd && reservationHourlyRate){
const oldReservationLength = oldEnd.diff(oldStart, 'hours', true);
const newReservationLength = newEnd.diff(newStart, 'hours', true);
const differenceFromNow = oldStart.diff(moment.utc(), 'hours');
if (differenceFromNow && (differenceFromNow < 24)){
// Changed reservation that was within 24hrs from now
// 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 (newReservationLength < oldReservationLength){
const differenceInLength = oldReservationLength - newReservationLength;
const chargeFee = differenceInLength*hourlyRate*BOOKING_CHANGE_PERCENTAGE_CHARGE/100;
const incident = {
reservationId,
memberId,
resourceId,
oldBookingStart: oldReservation.start,
oldBookingEnd: oldReservation.end,
newBookingStart: newReservation.start,
newBookingEnd: newReservation.end,
incidentType: incidentType.BOOKING_SHORTENED,
chargeFee,
};
incidents.push(incident);
}
}else{
// Reservation moved to another day
// Add cancellation charge
const chargeFee = oldReservationLength*hourlyRate*BOOKING_CHANGE_PERCENTAGE_CHARGE/100;
const incident = {
reservationId,
memberId,
resourceId,
oldBookingStart: oldReservation.start,
oldBookingEnd: oldReservation.end,
newBookingStart: newReservation.start,
newBookingEnd: newReservation.end,
incidentType: incidentType.BOOKING_MOVED_TO_ANOTHER_DAY,
chargeFee,
};
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));
}else{
reject('Input argument is not an array !');
}
});
};
module.exports = {
chargeBookingChanges,
};

View File

@@ -15,6 +15,10 @@ const fetchAllBookings = () => {
const bookingData = result && result.data ? result.data : [];
bookingData.forEach((fullBookingEntry) => {
const fees = fullBookingEntry && fullBookingEntry.fees ? fullBookingEntry.fees : [];
const firstFee = fees.length > 0 && fees[0].fee ? fees[0].fee : undefined;
const hourlyRate = firstFee && firstFee.price ? firstFee.price : undefined;
cleanedBookingReservations.push({
reservationId: fullBookingEntry['_id'],
memberId: fullBookingEntry.member,
@@ -24,6 +28,7 @@ const fetchAllBookings = () => {
end: fullBookingEntry.end.dateTime,
timezone: fullBookingEntry.timezone,
canceled: fullBookingEntry.canceled || false,
hourlyRate,
});
});
resolve(cleanedBookingReservations);
@@ -145,7 +150,19 @@ const getFirstReservationInBlock = (reservation) => {
};
const writeBookingReservation = (bookingReservation) => {
return db.bookingReservation.findOrCreate({where: {...bookingReservation}, defaults: {...bookingReservation}});
const { reservationId, memberId, officeId, resourceId, start, end, timezone, canceled, hourlyRate } = bookingReservation;
const bookingReservationForDB = {
reservationId,
memberId,
officeId,
resourceId,
start,
end,
timezone,
canceled,
hourlyRate,
};
return db.bookingReservation.findOrCreate({where: {...bookingReservationForDB}, defaults: {...bookingReservationForDB}});
};
const bulkWriteReservationsWithChangesTracking = (reservations) => {
@@ -157,28 +174,21 @@ const bulkWriteReservationsWithChangesTracking = (reservations) => {
const changedKeys = instance.changed();
const previous = instance.previous();
const indexOfUpdatedAt = changedKeys.indexOf('updatedAt');
const lookupKeys = ['start', 'end'];
if (indexOfUpdatedAt !== -1){
changedKeys.splice(indexOfUpdatedAt, 1);
}
if (changedKeys.length > 0){
//check if there is really difference, reservation start and end timestamps are reported as changed but they are not
let realChange = false;
changedKeys.forEach((changedKey) => {
if (JSON.stringify(previous[changedKey]) !== JSON.stringify(instance[changedKey])){
let realChange = false;
lookupKeys.forEach((key) => {
if ((changedKeys.indexOf(key) !== -1) &&
(JSON.stringify(previous[key]) !== JSON.stringify(instance[key]))){
realChange = true;
}
});
if (realChange){
changes.push({
oldReservation: previous,
newReservation: instance.get(),
});
}
});
if (realChange){
changes.push({
oldReservation: previous,
newReservation: instance.get(),
});
}
});