diff --git a/constants/constants.js b/constants/constants.js index 6291394..aaeff9f 100644 --- a/constants/constants.js +++ b/constants/constants.js @@ -57,6 +57,8 @@ const csvParserErrors = { const officeRnDAPIErrors = { FAILED_TO_FETCH_MEMBERS: 'Failed to fetch members', FAILED_TO_FETCH_BOOKINGS: 'Failed to fetch booking reservations', + FAILED_TO_CREATE_BOOKINGS: 'Failed to create booking reservations', + FAILED_TO_DELETE_BOOKINGS: 'Failed to delete booking reservations', FAILED_TO_FETCH_FEES: 'Failed to fetch existing fees from ORD', FAILED_TO_FETCH_PLANS: 'Failed to fetch plans from ORD', FAILED_TO_DELETE_FEES: 'Failed to delete fees in ORD', diff --git a/services/doorLock/doorLock.js b/services/doorLock/doorLock.js index 6ba62e7..08c7fb6 100644 --- a/services/doorLock/doorLock.js +++ b/services/doorLock/doorLock.js @@ -359,7 +359,7 @@ const getLockEntryForReservation = (reservation, nextReservation) => { const getEntriesBetween = (fromTimestamp, toTimestamp, resourceId) => { return new Promise((resolve, reject) => { - if (!fromTimestamp || !toTimestamp || !resourceId){ + if (!resourceId){ resolve([]); }else { const andTimestampFilters = []; diff --git a/services/integration/bookingChangeCharges.js b/services/integration/bookingChangeCharges.js index 597a632..1f6f2ba 100644 --- a/services/integration/bookingChangeCharges.js +++ b/services/integration/bookingChangeCharges.js @@ -146,7 +146,7 @@ const getIncidentsFromChanges = (changes) => { reservationsForAdditionalCheck.push(reservationId); } } else { - console.log('\t\tNo'); + // console.log('\t\tNo'); // Reservation moved to another day // Add cancellation charge const chargeFee = oldReservationLength * reservationHourlyRate * BOOKING_CHANGE_PERCENTAGE_CHARGE / 100; diff --git a/services/integration/checkBookingChange.js b/services/integration/checkBookingChange.js index 9a46a39..ea53ae2 100644 --- a/services/integration/checkBookingChange.js +++ b/services/integration/checkBookingChange.js @@ -49,10 +49,13 @@ const checkBookingChanges = () => { Promise.all(asyncActions) .then((asyncActionResults) => { const changes = asyncActionResults[1]; + // console.log(changes); bulkWriteChanges(changes) .then(() => { getIncidentsFromChanges(changes) .then(({incidents, reservationsForAdditionalCheck}) => { + // console.log('=====INCIDENTS====='); + // console.log(incidents); bulkWriteBookingChangeIncidents(incidents) .then(() => { getReservationsIncidentsForRemoval(reservationsForAdditionalCheck) diff --git a/services/integration/doorLockCharges.js b/services/integration/doorLockCharges.js index 98bbf28..0f08363 100644 --- a/services/integration/doorLockCharges.js +++ b/services/integration/doorLockCharges.js @@ -533,92 +533,123 @@ const getIncidentData = (reservation) => { getEntriesBetween(fromTimestamp, toTimestamp, resourceId) .then((entriesBetween) => { + // const reservationMoment = moment.tz(currentReservation.start, currentReservation.timezone); + // if (currentReservation.memberId === '5ce785af422bdd00967fb781' && reservationMoment.isAfter('2019-11-23 00:00:16+00')) { + // console.log('\r\n\r\n==== ANALYSE RESERVATION [GET INCIDENT DATA] ==== '); + // console.log('\tStart : ', reservationMoment.format('DD.MM, HH:mm')); + // console.log('\tEnd : ', moment.tz(currentReservation.end, currentReservation.timezone).format('DD.MM, HH:mm')); + // console.log('\t----------------------------------'); + // console.log('\tFirst previous reservation : is back to back [', previousReservationIsBackToBack ? 'T' : 'F', ']'); + // if (previousReservation) { + // console.log('\t\tStart : ', moment.tz(previousReservation.start, previousReservation.timezone).format('DD.MM, HH:mm')); + // console.log('\t\tEnd : ', moment.tz(previousReservation.end, previousReservation.timezone).format('DD.MM, HH:mm')); + // } else { + // console.log('\t\tNO PREVIOUS RESERVATION'); + // } + // + // console.log('\tFirst next reservation : is back to back [', nextReservationIsBackToBack ? 'T' : 'F', ']'); + // if (nextReservation) { + // console.log('\t\tStart : ', moment.tz(nextReservation.start, nextReservation.timezone).format('DD.MM, HH:mm')); + // console.log('\t\tEnd : ', moment.tz(nextReservation.end, nextReservation.timezone).format('DD.MM, HH:mm')); + // } else { + // console.log('\t\tNO NEXT RESERVATION'); + // } + // + // console.log('\t--------------------'); + // + // console.log('\tFrom timestamp : ', fromTimestamp ? moment.tz(fromTimestamp, UI_TIMEZONE).format('DD.MM, HH:mm') : '-'); + // console.log('\tTo timestamp : ', toTimestamp ? moment.tz(toTimestamp, UI_TIMEZONE).format('DD.MM, HH:mm') : '-'); + // console.log('\t--------------------'); + // } + let pairUnlockEntry = null; let pairLockEntry = null; //Inspect all entries and insert detected incidents entriesBetween.forEach((entry) => { - if (entry && entry.event){ - switch(entry.event){ - case doorLockEvents.USER_UNLOCKED: - if (!pairUnlockEntry){ - pairUnlockEntry = entry; - }else{ - const emptyReservation = { - reservationId: null, - start: null, - end: null, - }; + // console.log('\tEvent : ', entry.event); + // console.log('\tEvent : ', entry.timestamp ? moment.tz(entry.timestamp, UI_TIMEZONE).format('DD.MM, HH:mm') : '-'); + if (entry && entry.event){ + switch(entry.event){ + case doorLockEvents.USER_UNLOCKED: + if (!pairUnlockEntry){ + pairUnlockEntry = entry; + }else{ + const emptyReservation = { + reservationId: null, + start: null, + end: null, + }; + incidents.push({ + incidentType: incidentType.UNLOCKED_INCIDENT_STANDALONE, + reservation: emptyReservation, + unlockTimestamp: pairUnlockEntry.timestamp, + memberId: pairUnlockEntry.memberId, + resourceId, + }); + + pairLockEntry = null; + pairUnlockEntry = entry; + } + break; + case doorLockEvents.USER_LOCKED: + if (pairUnlockEntry && !pairLockEntry){ + pairLockEntry = entry; + const emptyReservation = { + reservationId: null, + start: null, + end: null, + }; + const unlockMoment = moment.utc(pairUnlockEntry.timestamp); + const lockMoment = moment.utc(pairLockEntry.timestamp); + + if (lockMoment.tz(UI_TIMEZONE).isSame(unlockMoment.tz(UI_TIMEZONE), 'day')){ + const timeDifference = lockMoment.diff(unlockMoment, 'minutes'); + const timeIntervalsToCharge = Math.floor(timeDifference / UNSCHEDULED_TIME_RESOLUTION); + const totalChargeFee = timeIntervalsToCharge * UNSCHEDULED_CHARGE_PRICE; + if (timeIntervalsToCharge > 0){ incidents.push({ - incidentType: incidentType.UNLOCKED_INCIDENT_STANDALONE, + incidentType: incidentType.UNSCHEDULED_INCIDENT_STANDALONE, reservation: emptyReservation, unlockTimestamp: pairUnlockEntry.timestamp, + lockTimestamp: pairLockEntry.timestamp, memberId: pairUnlockEntry.memberId, resourceId, + chargePrice: UNSCHEDULED_CHARGE_PRICE, + timeIntervalsToCharge, + totalChargeFee, }); - - pairLockEntry = null; - pairUnlockEntry = entry; } - break; - case doorLockEvents.USER_LOCKED: - if (pairUnlockEntry && !pairLockEntry){ - pairLockEntry = entry; - const emptyReservation = { - reservationId: null, - start: null, - end: null, - }; - const unlockMoment = moment.utc(pairUnlockEntry.timestamp); - const lockMoment = moment.utc(pairLockEntry.timestamp); + }else{ + const emptyReservation = { + reservationId: null, + start: null, + end: null, + }; - if (lockMoment.tz(UI_TIMEZONE).isSame(unlockMoment.tz(UI_TIMEZONE), 'day')){ - const timeDifference = lockMoment.diff(unlockMoment, 'minutes'); - const timeIntervalsToCharge = Math.floor(timeDifference / UNSCHEDULED_TIME_RESOLUTION); - const totalChargeFee = timeIntervalsToCharge * UNSCHEDULED_CHARGE_PRICE; - if (timeIntervalsToCharge > 0){ - incidents.push({ - incidentType: incidentType.UNSCHEDULED_INCIDENT_STANDALONE, - reservation: emptyReservation, - unlockTimestamp: pairUnlockEntry.timestamp, - lockTimestamp: pairLockEntry.timestamp, - memberId: pairUnlockEntry.memberId, - resourceId, - chargePrice: UNSCHEDULED_CHARGE_PRICE, - timeIntervalsToCharge, - totalChargeFee, - }); - } - }else{ - const emptyReservation = { - reservationId: null, - start: null, - end: null, - }; + incidents.push({ + incidentType: incidentType.UNLOCKED_INCIDENT_STANDALONE, + reservation: emptyReservation, + unlockTimestamp: pairUnlockEntry.timestamp, + memberId: pairUnlockEntry.memberId, + resourceId, + }); + } - incidents.push({ - incidentType: incidentType.UNLOCKED_INCIDENT_STANDALONE, - reservation: emptyReservation, - unlockTimestamp: pairUnlockEntry.timestamp, - memberId: pairUnlockEntry.memberId, - resourceId, - }); - } - - pairUnlockEntry = null; - pairLockEntry = null; - }else{ - if (!pairUnlockEntry){ - pairLockEntry = entry; - //Only lock entry, ignore now - } - pairLockEntry = null; - pairUnlockEntry = null; - } + pairUnlockEntry = null; + pairLockEntry = null; + }else{ + if (!pairUnlockEntry){ + pairLockEntry = entry; + //Only lock entry, ignore now + } + pairLockEntry = null; + pairUnlockEntry = null; } - } - }); + } + } + }); //Now wait also for "forgotToLockAsyncCheck" to finish diff --git a/services/officeRnD/bookings.js b/services/officeRnD/bookings.js index d7b508e..35b4fa5 100644 --- a/services/officeRnD/bookings.js +++ b/services/officeRnD/bookings.js @@ -5,7 +5,7 @@ const moment = require('moment-timezone'); const Op = require('sequelize').Op; const { API } = require('../../helpers/api'); -const { officeRnDAPIErrors, MAX_BACK_TO_BACK_DIFFERENCE } = require('../../constants/constants'); +const { officeRnDAPIErrors, MAX_BACK_TO_BACK_DIFFERENCE, UI_TIMEZONE } = require('../../constants/constants'); const fetchAllBookings = () => { return new Promise((resolve, reject) => { @@ -14,24 +14,119 @@ const fetchAllBookings = () => { const cleanedBookingReservations = []; 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 : 0; + const bookingsToCreate = []; + const bookingIdsToRemove = []; - cleanedBookingReservations.push({ - reservationId: fullBookingEntry['_id'], - memberId: fullBookingEntry.member, - officeId: fullBookingEntry.office, - resourceId: fullBookingEntry.resourceId, - start: fullBookingEntry.start.dateTime, - end: fullBookingEntry.end.dateTime, - timezone: fullBookingEntry.timezone, - canceled: fullBookingEntry.canceled || false, - hourlyRate, - }); + 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']); + } }); - resolve(cleanedBookingReservations); + + //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);