'use strict'; const moment = require('moment-timezone'); const db = require('../../models/index'); const Op = require('sequelize').Op; const { UI_TIMEZONE, BOOKING_CHANGE_PERCENTAGE_CHARGE, CHARGE_BOOKING_CHANGE_UNDER_TIME, ALLOWED_BOOKING_CANCELLATION_TIME, incidentType } = require('../../constants/constants'); const { getBookingChangeLogsForReservationId } = require('./bookingChangeLog'); 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 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){ const oldResourceId = oldReservation.resourceId; 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 reservationCreationTimestamp = newReservation.createdAt ? moment.utc(newReservation.createdAt) : null; const reservationTimezone = newReservation.timezone ? newReservation.timezone : UI_TIMEZONE; const reservationHourlyRate = oldReservation.hourlyRate ? oldReservation.hourlyRate : newReservation.hourlyRate; const canceled = newReservation.canceled; 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(), '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, oldResourceId: oldResourceId || resourceId, newResourceId: resourceId, oldBookingStart: oldReservation.start, oldBookingEnd: oldReservation.end, newBookingStart: newReservation.start, newBookingEnd: newReservation.end, incidentType: incidentType.BOOKING_SHORTENED, chargeFee, }; 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, oldResourceId: oldResourceId || resourceId, newResourceId: 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{ // 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, oldResourceId: oldResourceId || resourceId, newResourceId: null, oldBookingStart: oldReservation.start, oldBookingEnd: oldReservation.end, newBookingStart: null, newBookingEnd: null, incidentType: incidentType.BOOKING_CANCELED_LATE, chargeFee, }; incidents.push(incident); } } } else{ // console.log('\t\tNo'); } } } }); 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']; return db.bookingChangeIncident.findAll({attributes, where: filters}); }; 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, };