374 lines
15 KiB
JavaScript
374 lines
15 KiB
JavaScript
'use strict';
|
|
|
|
const db = require('../../models/index');
|
|
const moment = require('moment-timezone');
|
|
const Op = require('sequelize').Op;
|
|
|
|
const { API } = require('../../helpers/api');
|
|
const { officeRnDAPIErrors, MAX_BACK_TO_BACK_DIFFERENCE, UI_TIMEZONE } = require('../../constants/constants');
|
|
|
|
const fetchAllBookings = () => {
|
|
return new Promise((resolve, reject) => {
|
|
API.get('/bookings')
|
|
.then((result) => {
|
|
const cleanedBookingReservations = [];
|
|
const bookingData = result && result.data ? result.data : [];
|
|
|
|
const bookingsToCreate = [];
|
|
const bookingIdsToRemove = [];
|
|
|
|
bookingData.forEach(fullBookingEntry => {
|
|
if (!fullBookingEntry){
|
|
return;
|
|
}
|
|
const fees = fullBookingEntry.fees ? fullBookingEntry.fees : [];
|
|
|
|
if (fees.length > 1){
|
|
// Recurring booking, let's create new booking
|
|
const member = fullBookingEntry.member ? fullBookingEntry.member : null;
|
|
const office = fullBookingEntry.office ? fullBookingEntry.office : null;
|
|
const resourceId = fullBookingEntry.resourceId ? fullBookingEntry.resourceId : null;
|
|
const team = fullBookingEntry.team ? fullBookingEntry.team : null;
|
|
const organization = fullBookingEntry.organization ? fullBookingEntry.organization : null;
|
|
const plan = fullBookingEntry.plan ? fullBookingEntry.plan : null;
|
|
const timezone = fullBookingEntry.timezone ? fullBookingEntry.timezone : UI_TIMEZONE;
|
|
const source = 'admin';
|
|
|
|
const startMoment = fullBookingEntry && fullBookingEntry.start && fullBookingEntry.start.dateTime ?
|
|
moment.utc(fullBookingEntry.start.dateTime) : null;
|
|
const endMoment = fullBookingEntry && fullBookingEntry.end && fullBookingEntry.end.dateTime ?
|
|
moment.utc(fullBookingEntry.end.dateTime) : null;
|
|
|
|
fees.forEach(fee => {
|
|
const dateMoment = fee.date ? moment.utc(fee.date) : null;
|
|
|
|
if (startMoment && endMoment && dateMoment){
|
|
const yearPart = dateMoment.year();
|
|
const monthPart = dateMoment.month();
|
|
const dayPart = dateMoment.date();
|
|
|
|
const newStartMoment = startMoment.clone().tz(fullBookingEntry.timezone).year(yearPart).month(monthPart).date(dayPart);
|
|
const newEndMoment = endMoment.clone().tz(fullBookingEntry.timezone).year(yearPart).month(monthPart).date(dayPart);
|
|
|
|
bookingsToCreate.push({
|
|
start: {
|
|
dateTime: newStartMoment.toISOString()
|
|
},
|
|
end: {
|
|
dateTime: newEndMoment.toISOString()
|
|
},
|
|
team,
|
|
member,
|
|
resourceId,
|
|
office,
|
|
source,
|
|
timezone,
|
|
organization,
|
|
plan
|
|
})
|
|
}
|
|
});
|
|
|
|
bookingIdsToRemove.push(fullBookingEntry['_id']);
|
|
}
|
|
});
|
|
|
|
//Here we now have possible bookings to create and then load again "check Booking changes"
|
|
|
|
if (bookingIdsToRemove.length > 0){
|
|
//First delete, wait until operation is done, than create bookings (to avoid conflicting date/time)
|
|
API.delete('bookings/?silent', { data: bookingIdsToRemove })
|
|
.then(() => {
|
|
//Now, insert new bookings
|
|
API.post('bookings/?silent', bookingsToCreate)
|
|
.then(() => {
|
|
//And fetch again all bookings
|
|
resolve(fetchAllBookings());
|
|
})
|
|
.catch((error) => {
|
|
console.log(officeRnDAPIErrors.FAILED_TO_CREATE_BOOKINGS);
|
|
console.log('Details : ', error);
|
|
reject(officeRnDAPIErrors.FAILED_TO_CREATE_BOOKINGS);
|
|
});
|
|
})
|
|
.catch(error => {
|
|
console.log(officeRnDAPIErrors.FAILED_TO_DELETE_BOOKINGS);
|
|
console.log('Details : ', error);
|
|
reject(officeRnDAPIErrors.FAILED_TO_DELETE_BOOKINGS);
|
|
});
|
|
}else{
|
|
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 : 0;
|
|
|
|
const startMoment = fullBookingEntry && fullBookingEntry.start && fullBookingEntry.start.dateTime ?
|
|
moment.utc(fullBookingEntry.start.dateTime) : null;
|
|
const endMoment = fullBookingEntry && fullBookingEntry.end && fullBookingEntry.end.dateTime ?
|
|
moment.utc(fullBookingEntry.end.dateTime) : null;
|
|
|
|
// console.log('\r\n\r\nStart : ', startMoment.clone().tz(fullBookingEntry.timezone).format('DD.MM. HH:mm'), '[', startMoment.toISOString(),']');
|
|
// console.log('End : ', endMoment.clone().tz(fullBookingEntry.timezone).format('DD.MM. HH:mm'), '[', endMoment.toISOString(), ']');
|
|
// console.log('Fees : ');
|
|
|
|
if (startMoment && endMoment){
|
|
cleanedBookingReservations.push({
|
|
reservationId: fullBookingEntry['_id'],
|
|
memberId: fullBookingEntry.member,
|
|
officeId: fullBookingEntry.office,
|
|
resourceId: fullBookingEntry.resourceId,
|
|
start: startMoment.toISOString(),
|
|
end: endMoment.toISOString(),
|
|
timezone: fullBookingEntry.timezone,
|
|
canceled: fullBookingEntry.canceled || false,
|
|
hourlyRate,
|
|
});
|
|
}
|
|
});
|
|
resolve(cleanedBookingReservations);
|
|
}
|
|
})
|
|
.catch((error) => {
|
|
console.log(officeRnDAPIErrors.FAILED_TO_FETCH_BOOKINGS);
|
|
console.log('Details : ', error);
|
|
reject(officeRnDAPIErrors.FAILED_TO_FETCH_BOOKINGS);
|
|
});
|
|
});
|
|
};
|
|
|
|
const getAllFinishedBookings = () => {
|
|
const attributes = ['reservationId', 'memberId', 'resourceId', 'start', 'end', 'timezone'];
|
|
const filters = {
|
|
canceled: false,
|
|
end: {
|
|
[Op.lt]: moment.utc().toISOString()
|
|
}
|
|
};
|
|
|
|
return db.bookingReservation.findAll({
|
|
attributes,
|
|
where: filters,
|
|
order: [
|
|
['start', 'ASC'],
|
|
]
|
|
})
|
|
};
|
|
|
|
const getFirstNextBooking = (reservation) => {
|
|
return new Promise ((resolve, reject) => {
|
|
const { resourceId, start } = reservation;
|
|
|
|
const attributes = ['reservationId', 'memberId', 'resourceId', 'start', 'end', 'timezone'];
|
|
const filters = {
|
|
canceled: false,
|
|
start: {
|
|
[Op.gt]: start
|
|
},
|
|
resourceId,
|
|
};
|
|
const order = [['start', 'ASC']];
|
|
|
|
db.bookingReservation.findAll({
|
|
attributes,
|
|
where: filters,
|
|
order,
|
|
})
|
|
.then((reservations) => {
|
|
if (reservations && reservations[0]){
|
|
resolve(reservations[0]);
|
|
}else{
|
|
resolve(undefined);
|
|
}
|
|
})
|
|
.catch((error) => reject(error));
|
|
});
|
|
};
|
|
|
|
const getFirstPreviousBooking = (reservation) => {
|
|
return new Promise ((resolve, reject) => {
|
|
const { resourceId, start } = reservation;
|
|
|
|
const attributes = ['reservationId', 'memberId', 'resourceId', 'start', 'end', 'timezone'];
|
|
const filters = {
|
|
canceled: false,
|
|
end: {
|
|
[Op.lte]: start
|
|
},
|
|
resourceId,
|
|
};
|
|
const order = [['end', 'DESC']];
|
|
|
|
db.bookingReservation.findAll({
|
|
attributes,
|
|
where: filters,
|
|
order,
|
|
})
|
|
.then((reservations) => {
|
|
if (reservations && reservations[0]){
|
|
resolve(reservations[0]);
|
|
}else{
|
|
resolve(undefined);
|
|
}
|
|
})
|
|
.catch((error) => reject(error));
|
|
});
|
|
};
|
|
|
|
const getFirstReservationInBlock = (reservation) => {
|
|
return new Promise ((resolve, reject) => {
|
|
const { resourceId, memberId, start } = reservation;
|
|
|
|
const fromTimestamp = moment.utc(start).subtract(MAX_BACK_TO_BACK_DIFFERENCE).toISOString();
|
|
const toTimestamp = start;
|
|
|
|
const filters = {
|
|
resourceId,
|
|
memberId,
|
|
end: {
|
|
[Op.and]: [
|
|
{[Op.gte]: fromTimestamp},
|
|
{[Op.lte]: toTimestamp}
|
|
]
|
|
}
|
|
};
|
|
|
|
db.bookingReservation.findOne({where: filters})
|
|
.then((previousReservation) => {
|
|
if (!previousReservation) {
|
|
resolve(reservation);
|
|
} else {
|
|
resolve(getFirstReservationInBlock(previousReservation));
|
|
}
|
|
})
|
|
.catch((error) => reject(error));
|
|
});
|
|
};
|
|
|
|
const writeBookingReservation = (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, resourcesMap) => {
|
|
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', 'resourceId', 'canceled'];
|
|
|
|
let realChange = false;
|
|
lookupKeys.forEach((key) => {
|
|
if ((changedKeys.indexOf(key) !== -1) &&
|
|
(JSON.stringify(previous[key]) !== JSON.stringify(instance[key]))){
|
|
realChange = true;
|
|
}
|
|
});
|
|
|
|
const previousResourceId = instance.previous('resourceId');
|
|
const currentResourceId = instance.resourceId;
|
|
|
|
const resourceId = currentResourceId ? currentResourceId : previousResourceId;
|
|
|
|
if (instance.hourlyRate === 0 || isNaN(instance.hourlyRate)){
|
|
if (parseFloat(instance.previous('hourlyRate') > 0)) {
|
|
instance.setDataValue('hourlyRate', instance.previous('hourlyRate'));
|
|
}else{
|
|
//Determine if we should apply weekend price or work day price
|
|
const newStartWeekDay = moment.utc(instance.start).isoWeekday();
|
|
const isWeekend = newStartWeekDay > 5; //6 - Saturday, 7 - Sunday
|
|
let hourlyRate;
|
|
if (isWeekend){
|
|
hourlyRate = resourceId ? resourcesMap[resourceId].price.weekendPrice : 0;
|
|
}else{
|
|
hourlyRate = resourceId ? resourcesMap[resourceId].price.price : 0;
|
|
}
|
|
instance.setDataValue('hourlyRate', hourlyRate);
|
|
}
|
|
}
|
|
|
|
if (realChange){
|
|
changes.push({
|
|
oldReservation: previous,
|
|
newReservation: instance.get(),
|
|
});
|
|
}
|
|
});
|
|
|
|
const newReservations = [];
|
|
const asyncReservationUpdate = (reservation) => {
|
|
return new Promise((resolve, reject) => {
|
|
db.bookingReservation.update(reservation, {
|
|
where: {
|
|
reservationId: reservation.reservationId,
|
|
},
|
|
returning: true,
|
|
individualHooks: true,
|
|
})
|
|
.then(([updateCount, updatedInstances]) => {
|
|
try {
|
|
if (updateCount === 0) {
|
|
const oldReservation = {
|
|
start: null,
|
|
end: null,
|
|
resourceId: null,
|
|
};
|
|
|
|
changes.push({
|
|
oldReservation,
|
|
newReservation: reservation,
|
|
});
|
|
|
|
newReservations.push(reservation);
|
|
}
|
|
resolve();
|
|
}catch (e) {
|
|
console.log('CATCH E : ', e);
|
|
reject(e);
|
|
}
|
|
})
|
|
.catch((error) => {
|
|
console.log('Error updating');
|
|
console.log(error);
|
|
reject(error);
|
|
});
|
|
});
|
|
};
|
|
reservations.forEach((reservation) => asyncJobs.push(asyncReservationUpdate(reservation)));
|
|
|
|
Promise.all(asyncJobs)
|
|
.then(() => {
|
|
db.bookingReservation.removeHook('updateHook');
|
|
|
|
db.bookingReservation.bulkCreate(newReservations)
|
|
.then(() => resolve(changes))
|
|
.catch((error) => reject(error));
|
|
})
|
|
.catch((error) => reject(error));
|
|
});
|
|
};
|
|
|
|
module.exports = {
|
|
fetchAllBookings,
|
|
writeBookingReservation,
|
|
getAllFinishedBookings,
|
|
getFirstNextBooking,
|
|
getFirstPreviousBooking,
|
|
getFirstReservationInBlock,
|
|
bulkWriteReservationsWithChangesTracking,
|
|
};
|