From 1447d800bf89f01badb5d75692a6e66ba8b83764 Mon Sep 17 00:00:00 2001 From: Senad Uka Date: Mon, 26 Aug 2019 05:03:16 +0200 Subject: [PATCH] Fix bug with calendar --- ...lumn-for-booking-change-incidents-table.js | 15 ++ models/bookingChangeIncident.js | 1 + models/bookingReservation.js | 1 + services/integration/bookingChangeCharges.js | 156 ++++++++++++++++-- services/integration/bookingChangeLog.js | 15 +- services/integration/checkBookingChange.js | 32 +++- services/integration/invoiceIntegration.js | 18 +- services/integration/reports.js | 6 +- services/officeRnD/bookings.js | 60 ++++--- 9 files changed, 245 insertions(+), 59 deletions(-) create mode 100644 migrations/20190822090157-add-deleted-column-for-booking-change-incidents-table.js diff --git a/migrations/20190822090157-add-deleted-column-for-booking-change-incidents-table.js b/migrations/20190822090157-add-deleted-column-for-booking-change-incidents-table.js new file mode 100644 index 0000000..aa84032 --- /dev/null +++ b/migrations/20190822090157-add-deleted-column-for-booking-change-incidents-table.js @@ -0,0 +1,15 @@ +'use strict'; + +module.exports = { + up: (queryInterface, Sequelize) => { + return queryInterface.addColumn('bookingChangeIncidents', 'deleted', { + type: Sequelize.BOOLEAN, + defaultValue: false, + after: 'chargeFee', + }); + }, + + down: (queryInterface, Sequelize) => { + return queryInterface.removeColumn('bookingChangeIncidents', 'deleted'); + } +}; diff --git a/models/bookingChangeIncident.js b/models/bookingChangeIncident.js index a5e9a75..5ed9ddc 100644 --- a/models/bookingChangeIncident.js +++ b/models/bookingChangeIncident.js @@ -12,6 +12,7 @@ module.exports = (sequelize, DataTypes) => { newBookingEnd: DataTypes.DATE, incidentType: DataTypes.INTEGER, chargeFee: DataTypes.FLOAT, + deleted: DataTypes.BOOLEAN, }, {}); bookingChangeIncident.associate = function(models) { // associations can be defined here diff --git a/models/bookingReservation.js b/models/bookingReservation.js index d6ba4c7..0fbdca3 100644 --- a/models/bookingReservation.js +++ b/models/bookingReservation.js @@ -5,6 +5,7 @@ module.exports = (sequelize, DataTypes) => { reservationId: { type: DataTypes.TEXT, primaryKey: true, + unique: true, }, memberId: DataTypes.TEXT, officeId: DataTypes.TEXT, diff --git a/services/integration/bookingChangeCharges.js b/services/integration/bookingChangeCharges.js index a880225..086d0b0 100644 --- a/services/integration/bookingChangeCharges.js +++ b/services/integration/bookingChangeCharges.js @@ -12,25 +12,55 @@ const { 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})); - }); +const { getBookingChangeLogsForReservationId } = require('./bookingChangeLog'); - Promise.all(asyncJobs) - .then(() => { - resolve(); +const getShorteningIncidentsForReservationId = (reservationId) => { + const filter = { + reservationId, + incidentType: incidentType.BOOKING_SHORTENED, + deleted: false, + }; + + return db.bookingChangeIncident.findAll({where: filter}); +}; + +const getReservationIncidentsAndLogs = (reservationId) => { + return new Promise((resolve, reject) => { + const asyncDataFetch = [getShorteningIncidentsForReservationId(reservationId), getBookingChangeLogsForReservationId(reservationId)]; + + Promise.all(asyncDataFetch) + .then((reservationData) => { + resolve({ + incidents: reservationData[0], + changeLogs: reservationData[1], + }); }) .catch((error) => reject(error)); }); }; -const chargeBookingChanges = (changes) => { +const bulkWriteBookingChangeIncidents = (incidents) => { + //TODO: Check if this complete method can be replaced with + // return db.bookingChangeIncident.bulkCreate(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 getIncidentsFromChanges = (changes) => { return new Promise((resolve, reject) => { if (Array.isArray(changes)){ const incidents = []; + const reservationsForAdditionalCheck = []; + changes.forEach((change) => { const { oldReservation, newReservation } = change; if (oldReservation && newReservation){ @@ -52,21 +82,45 @@ const chargeBookingChanges = (changes) => { const differenceFromNow = oldStart.diff(moment.utc(), 'minutes'); + // console.log('Change detected : '); + // console.log('\tOld reservation :'); + // console.log('\t\tResource : ', oldResourceId); + // console.log('\t\tStart : ', oldStart.format()); + // console.log('\t\tEnd : ', oldEnd.format()); + // console.log('\t\tLength : ', oldReservationLength); + // console.log('\tNew Reservation :'); + // console.log('\t\tResource : ', newReservation.resourceId); + // console.log('\t\tStart : ', newStart.format()); + // console.log('\t\tEnd : ', newEnd.format()); + // console.log('\t\tLength : ', newReservationLength); + // console.log('\t\tCanceled : ', canceled); + // console.log('\t---------------------------------'); + // console.log('\tDifference : ', differenceFromNow, 'minutes'); + // console.log(''); + // console.log('\tIs booking changed too late ? ', differenceFromNow, ' < ', CHARGE_BOOKING_CHANGE_UNDER_TIME); if (differenceFromNow < CHARGE_BOOKING_CHANGE_UNDER_TIME){ const { reservationId, memberId, resourceId } = newReservation; + // console.log('\t\tYes'); + + // console.log('\tIs booking canceled ?'); if (!canceled) { + // console.log('\t\tNo'); // Check if new reservation is on same day const sameDay = oldStart.tz(reservationTimezone).isSame(newStart.tz(reservationTimezone), 'day'); - + // console.log('\tIs new reservation on the same day ?'); if (sameDay) { + // console.log('\t\tYes'); // Reservation moved in same day // Check if member shortened the reservation + // console.log('\tIs reservation shortened ? ', newReservationLength, ' < ', oldReservationLength); if (newReservationLength < oldReservationLength) { + // console.log('\t\tYes'); const differenceInLength = oldReservationLength - newReservationLength; const chargeFee = differenceInLength * reservationHourlyRate * BOOKING_CHANGE_PERCENTAGE_CHARGE / 100; + // console.log('\t\t\tThis is [shortened] incident ! Charge fee : ', chargeFee); const incident = { reservationId, memberId, @@ -81,12 +135,15 @@ const chargeBookingChanges = (changes) => { }; incidents.push(incident); + }else{ + reservationsForAdditionalCheck.push(reservationId); } } else { + console.log('\t\tNo'); // Reservation moved to another day // Add cancellation charge const chargeFee = oldReservationLength * reservationHourlyRate * BOOKING_CHANGE_PERCENTAGE_CHARGE / 100; - + // console.log('\t\t\tThis is incident ! Charge fee : ', chargeFee); const incident = { reservationId, memberId, @@ -103,10 +160,15 @@ const chargeBookingChanges = (changes) => { incidents.push(incident); } }else{ + // console.log('\t\tYes'); const differenceFromCreation = moment.utc().diff(reservationCreationTimestamp, 'minutes'); + // console.log('\tIs booking created in past ', ALLOWED_BOOKING_CANCELLATION_TIME, ' minutes ?', differenceFromCreation, ' > ', ALLOWED_BOOKING_CANCELLATION_TIME); if (differenceFromCreation > ALLOWED_BOOKING_CANCELLATION_TIME){ + // console.log('\t\tYes'); const chargeFee = reservationHourlyRate * oldReservationLength * BOOKING_CHANGE_PERCENTAGE_CHARGE / 100; + + // console.log('\t\t\tThis is [cancellation] incident ! charge fee : ', chargeFee); const incident = { reservationId, memberId, @@ -124,22 +186,70 @@ const chargeBookingChanges = (changes) => { } } } + else{ + // console.log('\t\tNo'); + } } } }); - resolve(bulkWriteBookingChangeIncidents(incidents)); + + resolve({incidents, reservationsForAdditionalCheck}); }else{ reject('Input argument is not an array !'); } }); }; +const getReservationsIncidentsForRemoval = (reservations) => { + return new Promise((resolve, reject) => { + const asyncChecks = []; + reservations.forEach((reservationId) => { + asyncChecks.push(getReservationIncidentsAndLogs(reservationId)); + }); + + Promise.all(asyncChecks) + .then((possibleRemovals) => { + const incidentsToRemove = []; + possibleRemovals.forEach((possibleRemoval) => { + const { incidents, changeLogs } = possibleRemoval; + + if (incidents.length > 0){ + const initialReservation = changeLogs && changeLogs[0] ? changeLogs[0] : undefined; + const reservationLastChange = changeLogs && changeLogs[changeLogs.length-1] ? + changeLogs[changeLogs.length-1] : undefined; + + if (initialReservation && reservationLastChange){ + const initialNewStartMoment = moment.utc(initialReservation.newStart); + const initialNewEndMoment = moment.utc(initialReservation.newEnd); + const lastNewStartMoment = moment.utc(reservationLastChange.newStart); + const lastNewEndMoment = moment.utc(reservationLastChange.newEnd); + + if (initialNewStartMoment && initialNewEndMoment && + lastNewStartMoment && lastNewEndMoment){ + + const initialDuration = initialNewEndMoment.diff(initialNewStartMoment, 'hours', true); + const lastDuration = lastNewEndMoment.diff(lastNewStartMoment, 'hours', true); + + if (lastDuration > initialDuration){ + incidentsToRemove.push(...incidents); + } + } + } + } + }); + resolve(incidentsToRemove); + }) + .catch((error) => reject(error)); + }); +}; + const getChargedCanceledReservations = (reservationIds) => { const filters = { reservationId: { [Op.in]: reservationIds, }, incidentType: incidentType.BOOKING_CANCELED_LATE, + deleted: false, }; const attributes = ['memberId', 'oldBookingStart', 'oldBookingEnd', 'chargeFee']; @@ -147,7 +257,21 @@ const getChargedCanceledReservations = (reservationIds) => { return db.bookingChangeIncident.findAll({attributes, where: filters}); }; -module.exports = { - chargeBookingChanges, - getChargedCanceledReservations, +const deleteBookingChangeIncidents = (incidents) => { + const asyncActions = []; + incidents.forEach((incident) => { + incident.set('deleted', true); + asyncActions.push(incident.save()); + }); + + return Promise.all(asyncActions); +}; + +module.exports = { + getChargedCanceledReservations, + getIncidentsFromChanges, + bulkWriteBookingChangeIncidents, + getShorteningIncidentsForReservationId, + getReservationsIncidentsForRemoval, + deleteBookingChangeIncidents, }; diff --git a/services/integration/bookingChangeLog.js b/services/integration/bookingChangeLog.js index c35f5d6..be59dc3 100644 --- a/services/integration/bookingChangeLog.js +++ b/services/integration/bookingChangeLog.js @@ -2,7 +2,7 @@ const db = require('../../models/index'); -const bulkWriteChanges = ((changes) => { +const bulkWriteChanges = (changes) => { const changeLogsForDB = []; changes.forEach((change) => { @@ -48,8 +48,19 @@ const bulkWriteChanges = ((changes) => { return db.bookingReservationChangeLog.bulkCreate(changeLogsForDB); // console.log(changeLogsForDB); // return new Promise((resolve) => resolve()); -}); +}; + +const getBookingChangeLogsForReservationId = (reservationId) => { + const filter = { + reservationId, + }; + + const order = [['createdAt', 'ASC']]; + + return db.bookingReservationChangeLog.findAll({where: filter, order}); +}; module.exports = { bulkWriteChanges, + getBookingChangeLogsForReservationId, }; diff --git a/services/integration/checkBookingChange.js b/services/integration/checkBookingChange.js index 3606c3e..2bb1031 100644 --- a/services/integration/checkBookingChange.js +++ b/services/integration/checkBookingChange.js @@ -2,7 +2,12 @@ const { fetchAllBookings, bulkWriteReservationsWithChangesTracking } = require('../officeRnD/bookings'); -const { chargeBookingChanges } = require('./bookingChangeCharges'); +const { + getIncidentsFromChanges, + bulkWriteBookingChangeIncidents, + getReservationsIncidentsForRemoval, + deleteBookingChangeIncidents, +} = require('./bookingChangeCharges'); const { bulkWriteChanges } = require('./bookingChangeLog'); const checkBookingChanges = () => { @@ -13,9 +18,28 @@ const checkBookingChanges = () => { .then((changes) => { bulkWriteChanges(changes) .then(() => { - chargeBookingChanges(changes) - .then(() => { - resolve(true); + getIncidentsFromChanges(changes) + .then(({incidents, reservationsForAdditionalCheck}) => { + bulkWriteBookingChangeIncidents(incidents) + .then(() => { + getReservationsIncidentsForRemoval(reservationsForAdditionalCheck) + .then((incidentsToRemove) => { + deleteBookingChangeIncidents(incidentsToRemove) + .then(() => resolve(true)) + .catch((error) => { + console.log('Error deleting incidents : ', error); + reject(error); + }); + }) + .catch((error) => { + console.log('Failed to fetch reservations possible incidents for removal', error); + reject(error); + }); + }) + .catch((error) => { + console.log('error', error); + reject(error); + }); }) .catch((error) => { console.log('Error creating charges ', error); diff --git a/services/integration/invoiceIntegration.js b/services/integration/invoiceIntegration.js index 4c63322..2a4731a 100644 --- a/services/integration/invoiceIntegration.js +++ b/services/integration/invoiceIntegration.js @@ -65,7 +65,7 @@ const createFeeFromIncident = (incident) => { switch (incidentTypeNumber){ case incidentType.UNLOCKED_INCIDENT_RELATED_WITH_RESERVATION: roomExplanation = resourceName; - dateExplanation = bookingStartMoment.clone().startOf('day').format('MMM DD'); + dateExplanation = bookingStartMoment.clone().startOf('day').format('ddd, MMM DD'); bookingTimeExplanation = `${bookingStartMoment.clone().format('HH:mm a')} - ${bookingEndMoment.clone().format('HH:mm a')}`; incidentTimeExplanation = ''; // `UNLOCK : ${unlockMoment.clone().format('HH:mm a')}`; additionalIncidentExplanation = ` ${unlockedIncidentLevelsPrices[incidentLevel].description},`; @@ -79,7 +79,7 @@ const createFeeFromIncident = (incident) => { break; case incidentType.UNSCHEDULED_INCIDENT_BEFORE_RESERVATION: roomExplanation = resourceName; - dateExplanation = bookingStartMoment.clone().startOf('day').format('MMM DD'); + dateExplanation = bookingStartMoment.clone().startOf('day').format('ddd, MMM DD'); bookingTimeExplanation = `${bookingStartMoment.clone().format('HH:mm a')} - ${bookingEndMoment.clone().format('HH:mm a')}`; incidentTimeExplanation = ` Unlock : ${unlockMoment.clone().format('HH:mm a')},`; incidentTypeExplanation = ''; @@ -92,7 +92,7 @@ const createFeeFromIncident = (incident) => { break; case incidentType.UNSCHEDULED_INCIDENT_AFTER_RESERVATION: roomExplanation = resourceName; - dateExplanation = bookingStartMoment.clone().startOf('day').format('MMM DD'); + dateExplanation = bookingStartMoment.clone().startOf('day').format('ddd, MMM DD'); bookingTimeExplanation = `${bookingStartMoment.clone().format('HH:mm a')} - ${bookingEndMoment.clone().format('HH:mm a')}`; incidentTimeExplanation = ` Lock : ${lockMoment.clone().format('HH:mm a')},`; incidentTypeExplanation = ''; @@ -105,7 +105,7 @@ const createFeeFromIncident = (incident) => { break; case incidentType.UNLOCKED_INCIDENT_STANDALONE: roomExplanation = resourceName; - dateExplanation = unlockMoment.clone().startOf('day').format('MMM DD'); + dateExplanation = unlockMoment.clone().startOf('day').format('ddd, MMM DD'); bookingTimeExplanation = `No reservation`; incidentTimeExplanation = ''; // `UNLOCK : ${unlockMoment.clone().format('HH:mm a')}`; additionalIncidentExplanation = ` ${unlockedIncidentLevelsPrices[incidentLevel].description},`; @@ -119,7 +119,7 @@ const createFeeFromIncident = (incident) => { break; case incidentType.UNSCHEDULED_INCIDENT_STANDALONE: roomExplanation = resourceName; - dateExplanation = unlockMoment.clone().startOf('day').format('MMM DD'); + dateExplanation = unlockMoment.clone().startOf('day').format('ddd, MMM DD'); bookingTimeExplanation = `No reservation`; incidentTimeExplanation = ` Unlock : ${unlockMoment.clone().format('HH:mm a')} Lock : ${lockMoment.clone().format('HH:mm a')},`; incidentTypeExplanation = ''; @@ -137,7 +137,7 @@ const createFeeFromIncident = (incident) => { roomExplanation = oldResourceName; } - dateExplanation = `${oldBookingStartMoment.clone().format('MMM DD')} -> ${newBookingStartMoment.clone().format('MMM DD')}`; + dateExplanation = `${oldBookingStartMoment.clone().format('ddd, MMM DD')} -> ${newBookingStartMoment.clone().format('ddd, MMM DD')}`; bookingTimeExplanation = `(${oldBookingStartMoment.clone().format('HH:mm a')} - ${oldBookingEndMoment.clone().format('HH:mm a')}) -> (${newBookingStartMoment.clone().format('HH:mm a')} - ${newBookingEndMoment.clone().format('HH:mm a')})`; incidentTimeExplanation = ` Moved on : ${incidentTimestampMoment.clone().format('MMM DD, HH:mm a')},`; incidentTypeExplanation = '[Cancellation]'; @@ -155,7 +155,7 @@ const createFeeFromIncident = (incident) => { roomExplanation = oldResourceName; } - dateExplanation = `${oldBookingStartMoment.clone().format('MMM DD')}`; + dateExplanation = `${oldBookingStartMoment.clone().format('ddd, MMM DD')}`; bookingTimeExplanation = `(${oldBookingStartMoment.clone().format('HH:mm a')} - ${oldBookingEndMoment.clone().format('HH:mm a')}) -> (${newBookingStartMoment.clone().format('HH:mm a')} - ${newBookingEndMoment.clone().format('HH:mm a')})`; incidentTimeExplanation = ` Shortened on : ${incidentTimestampMoment.clone().format('MMM DD, HH:mm a')},`; incidentTypeExplanation = '[Cancellation]'; @@ -168,7 +168,7 @@ const createFeeFromIncident = (incident) => { break; case incidentType.BOOKING_CANCELED_LATE: roomExplanation = oldResourceName; - dateExplanation = `${oldBookingStartMoment.clone().format('MMM DD')}`; + dateExplanation = `${oldBookingStartMoment.clone().format('ddd, MMM DD')}`; bookingTimeExplanation = `${oldBookingStartMoment.clone().format('HH:mm a')} - ${oldBookingEndMoment.clone().format('HH:mm a')}`; incidentTimeExplanation = ` Canceled on : ${incidentTimestampMoment.clone().format('MMM DD, HH:mm a')},`; incidentTypeExplanation = '[Cancellation]'; @@ -181,7 +181,7 @@ const createFeeFromIncident = (incident) => { break; } - const formattedName = `${incidentTypeExplanation} ${incidentExplanation},${additionalIncidentExplanation}${incidentTimeExplanation} ${officeName}, ${roomExplanation}, ${dateExplanation}, ${bookingTimeExplanation}`; + const formattedName = `${dateExplanation}, ${bookingTimeExplanation}, ${roomExplanation}, ${incidentExplanation}, ${officeName}`; return { name: formattedName, diff --git a/services/integration/reports.js b/services/integration/reports.js index ae79c34..e5e0154 100644 --- a/services/integration/reports.js +++ b/services/integration/reports.js @@ -136,7 +136,9 @@ const getBookingChangeIncidents = (startDate, endDate, memberIds) => { 'createdAt' ]; - const filters = {}; + const filters = { + deleted: false, + }; if (startDate && endDate) { filters.createdAt = { @@ -280,6 +282,7 @@ const getAllIncidents = (dateRange, memberIds) => { newBookingEnd, incidentType, chargeFee, + deleted, createdAt, } = bookingChangeIncident; const memberName = membersMap[memberId].name; @@ -307,6 +310,7 @@ const getAllIncidents = (dateRange, memberIds) => { newBookingEndRaw: newBookingEnd, incidentType, totalChargeFee: chargeFee, + deleted, incidentTimestamp: formatTime(createdAt), incidentTimestampRaw: createdAt, }); diff --git a/services/officeRnD/bookings.js b/services/officeRnD/bookings.js index 2f18f0c..0ae97e5 100644 --- a/services/officeRnD/bookings.js +++ b/services/officeRnD/bookings.js @@ -194,18 +194,19 @@ const bulkWriteReservationsWithChangesTracking = (reservations) => { } }); - reservations.forEach((reservation) => { - const asyncReservationUpdate = () => { - return new Promise((resolve, reject) => { - db.bookingReservation.update(reservation, { - where: { - reservationId: reservation.reservationId, - }, - returning: true, - individualHooks: true, - }) - .then(([updateCount, updatedInstances]) => { - if (updateCount === 0){ + 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, @@ -216,26 +217,31 @@ const bulkWriteReservationsWithChangesTracking = (reservations) => { oldReservation, newReservation: reservation, }); - resolve(db.bookingReservation.upsert(reservation)); - }else{ - resolve(); - } - }) - .catch((error) => { - console.log('Error updating'); - console.log(error); - reject(error); - }) - }); - }; - asyncJobs.push(asyncReservationUpdate()); - }); + 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'); - resolve(changes); + + db.bookingReservation.bulkCreate(newReservations) + .then(() => resolve(changes)) + .catch((error) => reject(error)); }) .catch((error) => reject(error)); });