Add cancelation charges

This commit is contained in:
Senad Uka
2019-07-08 20:37:14 +02:00
parent 1e1c61882f
commit 9d96ac4772
21 changed files with 575 additions and 64 deletions

View File

@@ -0,0 +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

@@ -111,6 +111,45 @@ const getUnscheduledIncidents = (startDate, endDate, memberId) => {
});
};
const getBookingChangeIncidents = (startDate, endDate, memberId) => {
const attributes = [
'id',
'reservationId',
'memberId',
'resourceId',
'oldBookingStart',
'oldBookingEnd',
'newBookingStart',
'newBookingEnd',
'incidentType',
'chargeFee',
'createdAt'
];
const filters = {};
if (startDate && endDate) {
filters.createdAt = {
[Op.and]: {
[Op.gte]: startDate.toISOString(),
[Op.lte]: endDate.toISOString(),
}
}
}
if (memberId){
filters.memberId = memberId;
}
return db.bookingChangeIncident.findAll({
attributes,
where: filters,
sort: [
['createdAt', 'ASC']
]
});
};
const formatTime = (timestamp) => {
const momentObject = moment.tz(timestamp, UI_TIMEZONE);
if (momentObject.isValid()){
@@ -120,13 +159,13 @@ const formatTime = (timestamp) => {
}
};
const getAllDoorLockIncidents = (dateRange, memberId) => {
const getAllIncidents = (dateRange, memberId) => {
return new Promise ((resolve, reject) => {
let startDate, endDate;
if (dateRange.startDate && dateRange.endDate){
startDate = moment.tz(dateRange.startDate, DEFAULT_DATE_FORMAT, UI_TIMEZONE);
endDate = moment.tz(dateRange.endDate, DEFAULT_DATE_FORMAT, UI_TIMEZONE);
startDate = moment.tz(dateRange.startDate, DEFAULT_DATE_FORMAT, UI_TIMEZONE).startOf('day');
endDate = moment.tz(dateRange.endDate, DEFAULT_DATE_FORMAT, UI_TIMEZONE).endOf('day');
if (!startDate.isValid() || !endDate.isValid() || endDate.isBefore(startDate)){
reject(integrationServiceErrors.INVALID_DATE_RANGE);
@@ -134,7 +173,14 @@ const getAllDoorLockIncidents = (dateRange, memberId) => {
}
}
const dataFetchJobs = [fetchAllMembers(), fetchOffices(), fetchResources(), getUnlockedIncidents(startDate, endDate, memberId), getUnscheduledIncidents(startDate, endDate, memberId)];
const dataFetchJobs = [
fetchAllMembers(),
fetchOffices(),
fetchResources(),
getUnlockedIncidents(startDate, endDate, memberId),
getUnscheduledIncidents(startDate, endDate, memberId),
getBookingChangeIncidents(startDate, endDate, memberId)
];
Promise.all(dataFetchJobs)
.then((data) => {
@@ -143,6 +189,7 @@ const getAllDoorLockIncidents = (dateRange, memberId) => {
const resources = data[2];
const unlockedIncidents = data[3];
const unscheduledIncidents = data[4];
const bookingChangeIncidents = data[5];
const membersMap = {};
const officesMap = {};
@@ -200,6 +247,39 @@ const getAllDoorLockIncidents = (dateRange, memberId) => {
});
});
bookingChangeIncidents.forEach((bookingChangeIncident) => {
const {
id,
memberId,
resourceId,
oldBookingStart,
oldBookingEnd,
newBookingStart,
newBookingEnd,
incidentType,
chargeFee,
createdAt,
} = bookingChangeIncident;
const memberName = membersMap[memberId].name;
const resource = resourcesMap[resourceId];
const resourceName = resource.resourceName;
const officeName = officesMap[resource.officeId].officeName;
allIncidents.push({
incidentId: id,
memberId,
memberName,
resourceName,
officeName,
oldBookingStart: formatTime(oldBookingStart),
oldBookingEnd: formatTime(oldBookingEnd),
newBookingStart: formatTime(newBookingStart),
newBookingEnd: formatTime(newBookingEnd),
incidentType,
totalChargeFee: chargeFee,
incidentTimestamp: formatTime(createdAt),
});
});
resolve(allIncidents);
})
.catch((error) => reject(error));
@@ -209,5 +289,5 @@ const getAllDoorLockIncidents = (dateRange, memberId) => {
module.exports = {
getUnlockedIncidents,
getUnscheduledIncidents,
getAllDoorLockIncidents,
getAllIncidents,
};

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,77 @@ 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) => {
return new Promise ((resolve, reject) => {
const changes = [];
const asyncJobs = [];
db.bookingReservation.addHook('beforeUpdate', 'updateHook', (instance) => {
const changedKeys = instance.changed();
const previous = instance.previous();
const lookupKeys = ['start', 'end'];
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(),
});
}
});
reservations.forEach((reservation) => {
asyncJobs.push(
db.bookingReservation.update(reservation, {
where: {
reservationId: reservation.reservationId,
},
returning: true,
individualHooks: true,
})
.then(([updateCount, updatedInstances]) => {
if (updateCount === 0){
db.bookingReservation.upsert(reservation);
}
})
.catch((error) => {
console.log('Error updating');
console.log(error);
reject(error);
})
);
});
Promise.all(asyncJobs)
.then(() => {
db.bookingReservation.removeHook('updateHook');
resolve(changes);
})
.catch((error) => reject(error));
});
};
module.exports = {
@@ -154,5 +229,6 @@ module.exports = {
getAllFinishedBookings,
getFirstNextBooking,
getFirstPreviousBooking,
getFirstReservationInBlock
getFirstReservationInBlock,
bulkWriteReservationsWithChangesTracking,
};