From 1d9d5ac684952872d08287b77de0df23b1910f31 Mon Sep 17 00:00:00 2001 From: Senad Uka Date: Mon, 1 Jul 2019 09:31:13 +0200 Subject: [PATCH] Fix problems with door unlock charges --- constants/constants.js | 7 + environment.env | 2 +- services/doorLock/doorLock.js | 205 +++++++++++--- services/integration/doorLockCharges.js | 349 ++++++++++++++---------- services/officeRnD/bookings.js | 41 ++- 5 files changed, 419 insertions(+), 185 deletions(-) diff --git a/constants/constants.js b/constants/constants.js index 812c38a..372674a 100644 --- a/constants/constants.js +++ b/constants/constants.js @@ -69,6 +69,10 @@ const UI_TIMEZONE = process.env.UI_TIMEZONE || 'America/Los_Angeles'; const DEFAULT_DATE_FORMAT = 'YYYY-MM-DD'; +const MAX_BACK_TO_BACK_DIFFERENCE = parseInt(process.env.MAX_BACK_TO_BACK_DIFFERENCE) || 0; +const UNSCHEDULED_TIME_RESOLUTION = parseInt(process.env.UNSCHEDULED_USE_TIME_RESOLUTION) || 5; +const UNSCHEDULED_CHARGE_PRICE = parseFloat(process.env.UNSCHEDULED_USE_CHARGE_PRICE) || 5; + module.exports = { VALID_CSV_HEADERS, USER_ENTRY_EVENT, @@ -82,4 +86,7 @@ module.exports = { incidentType, UI_TIMEZONE, DEFAULT_DATE_FORMAT, + MAX_BACK_TO_BACK_DIFFERENCE, + UNSCHEDULED_TIME_RESOLUTION, + UNSCHEDULED_CHARGE_PRICE, }; diff --git a/environment.env b/environment.env index 6d88ba9..f9172c0 100644 --- a/environment.env +++ b/environment.env @@ -8,7 +8,7 @@ EARLIEST_UNLOCK=Time in minutes UI_TIMEZONE=Timezone for user interface | https://en.wikipedia.org/wiki/List_of_tz_database_time_zones | example : America/Los_Angeles UNSCHEDULED_USE_TIME_RESOLUTION=Time in minutes -UNSCHEDULED_USE_CHARGE_FEE=Charge fee +UNSCHEDULED_USE_CHARGE_PRICE=Charge price UNLOCK_0=Price for unlocked door, first month UNLOCK_1=Price for unlocked door, second month diff --git a/services/doorLock/doorLock.js b/services/doorLock/doorLock.js index 803401e..2d96318 100644 --- a/services/doorLock/doorLock.js +++ b/services/doorLock/doorLock.js @@ -183,57 +183,186 @@ const writeDoorLockEvent = (entry) => { return db.doorLockEvent.findOrCreate({where: {...entry}, defaults: {...entry}}); }; -const getUnlockEntryForReservation = (reservation) => { - const { start, end, memberId, resourceId } = reservation; +const getUnlockEntryForReservation = (reservation, previousReservation) => { + return new Promise((resolve, reject) => { + const { memberId, resourceId } = reservation; - const attributes = ['memberName', 'event', 'timestamp']; - const earliestUnlock = parseInt(process.env.EARLIEST_UNLOCK) || 0; - const fromTimestamp = moment(start).subtract(earliestUnlock).toISOString(); - const toTimestamp = end; + const attributes = ['memberName', 'event', 'timestamp', 'resourceId']; - const filters = { - memberId, - timestamp: { - [Op.and]: [ - {[Op.gt]: fromTimestamp}, - {[Op.lte]: toTimestamp} - ] - }, - event: doorLockEvents.USER_UNLOCKED, - resourceId, - }; + const fromTimestamp = previousReservation && previousReservation.end ? + previousReservation.end : moment.utc(reservation.start).tz(UI_TIMEZONE).startOf('Day').toISOString(); + const toTimestamp = reservation.end; - return db.doorLockEvent.findOne({ - attributes, - where: filters, - }) + const filters = { + memberId, + timestamp: { + [Op.and]: [ + {[Op.gt]: fromTimestamp}, + {[Op.lte]: toTimestamp} + ] + }, + resourceId, + }; + const order = [['timestamp', 'DESC']]; + + db.doorLockEvent.findAll({ + attributes, + where: filters, + order, + }) + .then((entries) => { + let candidateUnlockEntry = null; + let pairedLockEntry = null; + let eventFound = false; + + // console.log('\r\n=?== SEARCH UNLOCK =='); + // console.log('ReservatioN : ', reservation.start); + + const entriesBeforeReservationStart = entries.filter((entry) => moment.utc(entry.timestamp).isBefore(reservation.start)); + + // console.log('After end : '); + /*console.log(entriesBeforeReservationStart.map(e => { + return { + timestamp: e.timestamp, + event: e.event, + } + })); + + */ + + entriesBeforeReservationStart.forEach((entry) => { + if (!eventFound) { + if (entry.event === doorLockEvents.USER_UNLOCKED) { + if (pairedLockEntry) { + pairedLockEntry = null; + candidateUnlockEntry = null; + } else { + candidateUnlockEntry = entry; + eventFound = true; + } + } + if (entry.event === doorLockEvents.USER_LOCKED){ + pairedLockEntry = entry; + } + } + }); + + if (eventFound){ + // console.log('FOUND : ', candidateUnlockEntry.timestamp, candidateUnlockEntry.event); + resolve(candidateUnlockEntry); + } else { + // console.log('NOT FOUND IN FIRST ROUND'); + candidateUnlockEntry = null; + const numberOfEntriesLeft = entries.length - entriesBeforeReservationStart.length; + const entriesAfterReservationStart = entries.slice(0, numberOfEntriesLeft); + + // console.log('Entries in reservation time : '); + /*console.log(entriesAfterReservationStart.map(e => { + return { + timestamp: e.timestamp, + event: e.event, + } + })); + + */ + + entriesAfterReservationStart.forEach((entry) => { + if (!eventFound) { + if (entry.event === doorLockEvents.USER_UNLOCKED) { + //console.log('FOUND : ', entry.timestamp, entry.event); + eventFound = true; + candidateUnlockEntry = entry; + } + } + }); + + //console.log('===??=='); + resolve(candidateUnlockEntry); + } + }) + .catch((error) => reject(error)); + }); }; -const getRelatedDoorLockEntries = (fromTimestamp, toTimestamp, memberId, resourceId) => { - const attributes = ['memberName', 'event', 'timestamp']; +const getLockEntryForReservation = (reservation, nextReservation) => { + return new Promise((resolve, reject) => { + const { memberId, resourceId } = reservation; - const filters = { - memberId, - timestamp: { - [Op.and]: [ - {[Op.gt]: fromTimestamp}, - {[Op.lte]: toTimestamp} + const attributes = ['memberName', 'event', 'timestamp']; + + const fromTimestamp = reservation.start; + const toTimestamp = nextReservation && nextReservation.start ? + nextReservation.start : moment.utc(reservation.start).tz(UI_TIMEZONE).endOf('Day').toISOString(); + + const filters = { + memberId, + timestamp: { + [Op.and]: [ + {[Op.gt]: fromTimestamp}, + {[Op.lte]: toTimestamp} ] - }, - event: doorLockEvents.USER_LOCKED, - resourceId, - }; + }, + resourceId, + }; - return db.doorLockEvent.findOne({ - attributes, - where: filters - }) + const order = [['timestamp', 'ASC']]; + + db.doorLockEvent.findAll({ + attributes, + where: filters, + order, + }) + .then((entries) => { + let candidateLockEntry = null; + let pairedUnlockEntry = null; + let eventFound = false; + + const entriesAfterReservationEnd = entries.filter((entry) => moment.utc(entry.timestamp).isAfter(reservation.end)); + + entriesAfterReservationEnd.forEach((entry) => { + if (!eventFound) { + if (entry.event === doorLockEvents.USER_LOCKED) { + if (pairedUnlockEntry) { + pairedUnlockEntry = null; + candidateLockEntry = null; + } else { + candidateLockEntry = entry; + eventFound = true; + } + } + if (entry.event === doorLockEvents.USER_UNLOCKED){ + pairedUnlockEntry = entry; + } + } + }); + + if (eventFound){ + resolve(candidateLockEntry); + } else { + candidateLockEntry = null; + const numberOfEntriesLeft = entries.length - entriesAfterReservationEnd.length; + const entriesBeforeReservationEnd = entries.slice(0, numberOfEntriesLeft); + + entriesBeforeReservationEnd.reverse().forEach((entry) => { + if (!eventFound) { + if (entry.event === doorLockEvents.USER_LOCKED) { + eventFound = true; + candidateLockEntry = entry; + } + } + }); + + resolve(candidateLockEntry); + } + }) + .catch((error) => reject(error)); + }); }; module.exports = { parseDoorLockDataFile, writeDoorLockEvent, - getRelatedDoorLockEntries, getUnlockEntryForReservation, + getLockEntryForReservation, }; diff --git a/services/integration/doorLockCharges.js b/services/integration/doorLockCharges.js index 2a8903e..8ead2f0 100644 --- a/services/integration/doorLockCharges.js +++ b/services/integration/doorLockCharges.js @@ -3,9 +3,9 @@ const moment = require('moment-timezone'); const db = require('../../models/index'); -const { incidentType, unlockedIncidentLevelsPrices } = require('../../constants/constants'); -const { getUnlockEntryForReservation, getRelatedDoorLockEntries } = require('../doorLock/doorLock'); -const { getFirstPreviousBooking, getFirstNextBooking, getAllFinishedBookings } = require('../officeRnD/bookings'); +const { incidentType, unlockedIncidentLevelsPrices, UNSCHEDULED_CHARGE_PRICE, MAX_BACK_TO_BACK_DIFFERENCE, UNSCHEDULED_TIME_RESOLUTION } = require('../../constants/constants'); +const { getUnlockEntryForReservation, getLockEntryForReservation } = require('../doorLock/doorLock'); +const { getAllFinishedBookings, getFirstReservationInBlock, getFirstPreviousBooking, getFirstNextBooking } = require('../officeRnD/bookings'); const getSortedIncidentsForMember = (memberId) => { const attributes = ['bookingStart', 'incidentLevel', 'incidentLevelPrice']; @@ -169,125 +169,199 @@ const setUnlockedIncidentsLevel = (incidentReservations) => { }); }; +const getUnlockEntryForBlockReservations = (lastReservation, firstReservation) => { + return new Promise ((resolve, reject) => { + analyseReservation(firstReservation) + .then((result) => { + const reservationBeforeFirstReservationInBlock = result.previousReservation; + const unlockEntryForFirstReservation = result.unlockEntry; + + if (unlockEntryForFirstReservation){ + resolve(unlockEntryForFirstReservation); + }else{ + const fromTimestamp = reservationBeforeFirstReservationInBlock ? + reservationBeforeFirstReservationInBlock.end : + moment.utc(firstReservation.start).tz(UI_TIMEZONE).startOf('Day').toISOString(); + const toTimestamp = lastReservation.end; + const { memberId, reservationId } = lastReservation; + + const filters = { + memberId, + reservationId, + event: doorLockEvents.USER_UNLOCKED, + timestamp: { + [Op.and]: [ + {[Op.gt]: fromTimestamp}, + {[Op.lte]: toTimestamp} + ] + }, + }; + + db.doorLockEvent.findOne({where: filters}) + .then((anyUnlockEntryInBlock) => { + resolve(anyUnlockEntryInBlock); + }) + .catch((error) => reject(error)); + } + }) + .catch((error) => reject(error)); + }); +}; + +const analyseReservation = (reservation) => { + return new Promise ((resolve, reject) => { + const getNearBookingsAsync = [ + getFirstPreviousBooking(reservation), + getFirstNextBooking(reservation) + ]; + + Promise.all(getNearBookingsAsync) + .then(([previousReservation, nextReservation]) => { + const getRelatedDoorLockEntriesAsync = [ + getUnlockEntryForReservation(reservation, previousReservation), + getLockEntryForReservation(reservation, nextReservation) + ]; + + Promise.all(getRelatedDoorLockEntriesAsync) + .then(([unlockEntry, lockEntry]) => { + const currentReservationStart = moment.utc(reservation.start); + const currentReservationEnd = moment.utc(reservation.end); + + let timeDifferenceFromPreviousReservation = MAX_BACK_TO_BACK_DIFFERENCE + 100; + if (previousReservation) { + const previousReservationEnd = moment.utc(previousReservation.end); + timeDifferenceFromPreviousReservation = currentReservationStart.diff(previousReservationEnd, 'minutes'); + } + const previousReservationIsBackToBack = timeDifferenceFromPreviousReservation < MAX_BACK_TO_BACK_DIFFERENCE; + + + let timeDifferenceBeforeNextReservation = MAX_BACK_TO_BACK_DIFFERENCE + 100; + if (nextReservation) { + const nextReservationStart = moment.utc(nextReservation.start); + timeDifferenceBeforeNextReservation = nextReservationStart.diff(currentReservationEnd, 'minutes'); + } + const nextReservationIsBackToBack = timeDifferenceBeforeNextReservation < MAX_BACK_TO_BACK_DIFFERENCE; + + let timeDifferenceFromUnlockEntry = 0; + if (unlockEntry) { + const unlockTime = moment.utc(unlockEntry.timestamp); + timeDifferenceFromUnlockEntry = currentReservationStart.diff(unlockTime, 'minutes'); + } + const timeIntervalsToChargeBefore = Math.floor(timeDifferenceFromUnlockEntry / UNSCHEDULED_TIME_RESOLUTION); + const totalChargeFeeBefore = timeIntervalsToChargeBefore * UNSCHEDULED_CHARGE_PRICE; + const chargeBefore = totalChargeFeeBefore > 0; + + let timeDifferenceFromLockEntry = 0; + if (lockEntry) { + const lockTime = moment.utc(lockEntry.timestamp); + timeDifferenceFromLockEntry = lockTime.diff(currentReservationEnd, 'minutes'); + } + const timeIntervalsToChargeAfter = Math.floor(timeDifferenceFromLockEntry / UNSCHEDULED_TIME_RESOLUTION); + const totalChargeFeeAfter = timeIntervalsToChargeAfter * UNSCHEDULED_CHARGE_PRICE; + const chargeAfter = totalChargeFeeAfter > 0; + + const result = { + currentReservation: reservation, + previousReservation, + nextReservation, + unlockEntry, + lockEntry, + previousReservationIsBackToBack, + nextReservationIsBackToBack, + timeDifferenceFromPreviousReservation, + timeDifferenceBeforeNextReservation, + timeDifferenceFromUnlockEntry, + timeDifferenceFromLockEntry, + chargeBefore, + chargeAfter, + totalChargeFeeBefore, + totalChargeFeeAfter, + timeIntervalsToChargeBefore, + timeIntervalsToChargeAfter, + }; + + resolve(result); + }) + .catch((error) => reject(error)); + }) + .catch((error) => reject(error)); + }); +}; + +const checkIfMemberEverEntered = (reservation) => { + return new Promise ((resolve, reject) => { + getFirstReservationInBlock(reservation) + .then((firstReservationInBlock) => { + getUnlockEntryForBlockReservations(reservation, firstReservationInBlock) + .then((unlockEntry) => { + resolve(!!unlockEntry); + }) + .catch((error) => reject(error)); + }) + .catch((error) => reject(error)); + }); +}; + const getIncidentData = (reservation) => { return new Promise ((resolve, reject) => { - getFirstNextBooking(reservation) - .then(nextReservation => { - const endOfTheDay = moment.tz(reservation.end, reservation.timezone).endOf('Day').toISOString(); - let doorLockEntriesEndTime = nextReservation ? nextReservation.start : endOfTheDay; + analyseReservation(reservation) + .then((result) => { + const { + currentReservation, + previousReservation, + nextReservation, + unlockEntry, + lockEntry, + previousReservationIsBackToBack, + nextReservationIsBackToBack, + timeDifferenceFromPreviousReservation, + timeDifferenceBeforeNextReservation, + timeDifferenceFromUnlockEntry, + timeDifferenceFromLockEntry, + chargeBefore, + chargeAfter, + totalChargeFeeBefore, + totalChargeFeeAfter, + timeIntervalsToChargeBefore, + timeIntervalsToChargeAfter, + } = result; + const incidents = []; - if (nextReservation){ - // Check if next reservations is immediately after (back to back reservation) - // If yes, then there is no need to check door lock entries related to this booking - const firstReservationEnd = moment.utc(reservation.end); - const secondReservationStart = moment.utc(nextReservation.start); - const timeDifference = Math.abs(secondReservationStart.diff(firstReservationEnd, 'minutes')); - - const maxBackToBackDifference = parseInt(process.env.MAX_BACK_TO_BACK_DIFFERENCE) || 0; - if (timeDifference <= maxBackToBackDifference){ - // It is back to back reservation, no need to check door lock entries - resolve({ - incidentType: incidentType.NOT_AN_INCIDENT, - }); - return; - } - } - // Find door lock entries related to this member, between booking start time and - // next booking start time OR end of the day - - getRelatedDoorLockEntries(reservation.start, doorLockEntriesEndTime, reservation.memberId, reservation.resourceId) - .then((lockEntry) => { - if (lockEntry){ - const timeResolution = parseInt(process.env.UNSCHEDULED_USE_TIME_RESOLUTION) || 5 - const chargePrice = parseFloat(process.env.UNSCHEDULED_USE_CHARGE_FEE) || 5; - - const reservationEndTime = moment.utc(reservation.end); - const lockedTime = moment.utc(lockEntry.timestamp); - const timeDifference = Math.abs(reservationEndTime.diff(lockedTime, 'minutes')); - - const timeIntervalsToCharge = Math.floor(timeDifference / timeResolution); - const totalChargeFee = timeIntervalsToCharge * chargePrice; - - if (timeIntervalsToCharge > 0){ - resolve({ - incidentType: incidentType.UNSCHEDULED_INCIDENT, - reservation, - lockEntry, - chargePrice, - timeIntervalsToCharge, - totalChargeFee, - }) - } else { - resolve({ - incidentType: incidentType.NOT_AN_INCIDENT, - }); - } - } else { - // Check if there is unlock entry for this reservation - getUnlockEntryForReservation(reservation) - .then((unlockEntry) => { - if (unlockEntry){ - // This is unlocked incident - resolve({ - incidentType: incidentType.UNLOCKED_INCIDENT, - reservation, - }); - }else{ - // Check if there is back-to-back booking before current one - getFirstPreviousBooking(reservation) - .then((previousReservation) => { - if (previousReservation){ - const previousReservationEnd = moment.utc(previousReservation.end); - const currentReservationStart = moment.utc(reservation.start); - const timeDifference = Math.abs(currentReservationStart.diff(previousReservationEnd, 'minutes')); - - const maxBackToBackDifference = parseInt(process.env.MAX_BACK_TO_BACK_DIFFERENCE) || 0; - if (timeDifference <= maxBackToBackDifference) { - resolve({ - incidentType: incidentType.UNLOCKED_INCIDENT, - reservation, - }); - }else{ - resolve({ - incidentType: incidentType.NOT_AN_INCIDENT, - }); - } - }else{ - resolve({ - incidentType: incidentType.NOT_AN_INCIDENT, - }); - } - }) - .catch((error) => { - console.log('Error finding first previous reservation', error); - resolve({ - error, - }); - }); - } - }) - .catch((error) => { - console.log('Error finding unlock entry', error); - resolve({ - error - }); - }); - } - }) - .catch((error) => { - console.log('Error finding related door lock entry', error); - resolve({ - error, - }); + // 1. Check if member entered before reservation start time + if (unlockEntry && chargeBefore && !previousReservationIsBackToBack) { + incidents.push({ + incidentType: incidentType.UNSCHEDULED_INCIDENT, + reservation, + doorLockEntry: unlockEntry, + chargePrice: UNSCHEDULED_CHARGE_PRICE, + timeIntervalsToCharge: timeIntervalsToChargeBefore, + totalChargeFee: totalChargeFeeBefore, }); + } + // 2. Check if member left after reservation end time + if (lockEntry && chargeAfter && !nextReservationIsBackToBack) { + incidents.push({ + incidentType: incidentType.UNSCHEDULED_INCIDENT, + reservation, + doorLockEntry: lockEntry, + chargePrice: UNSCHEDULED_CHARGE_PRICE, + timeIntervalsToCharge: timeIntervalsToChargeAfter, + totalChargeFee: totalChargeFeeAfter, + }); + } + + // 3. Check if member forgot to lock door + if (unlockEntry && !lockEntry && !nextReservationIsBackToBack){ + incidents.push({ + incidentType: incidentType.UNLOCKED_INCIDENT, + reservation, + }); + } + + resolve(incidents); }) - .catch((error) => { - console.log('Error finding first next booking', error); - resolve({ - error, - }); - }); + .catch((error) => reject(error)); }); }; @@ -304,28 +378,29 @@ const calculateDoorLockCharges = () => { }); Promise.all(asyncCheckForIncidents) - .then((results) => { - results.forEach((result) => { - if (result.error){ - console.log('Error checking incident : ', result.error); - }else if(result.incidentType) { - switch (result.incidentType) { - case incidentType.UNLOCKED_INCIDENT: - unlockedIncidents.push(result.reservation); - break; - case incidentType.UNSCHEDULED_INCIDENT: - const { reservation, lockEntry, chargePrice, timeIntervalsToCharge, totalChargeFee } = result; - unscheduledIncidents.push({ - reservation, - lockEntry, - chargePrice, - timeIntervalsToCharge, - totalChargeFee, - }); - break; + .then((allReservationsIncidents) => { + allReservationsIncidents.forEach((reservationIncidents) => { + reservationIncidents.forEach((incident) => { + if (incident.error) { + console.log('Error checking incident : ', incident.error); + } else if (incident.incidentType) { + switch (incident.incidentType) { + case incidentType.UNLOCKED_INCIDENT: + unlockedIncidents.push(incident.reservation); + break; + case incidentType.UNSCHEDULED_INCIDENT: + const {reservation, doorLockEntry, chargePrice, timeIntervalsToCharge, totalChargeFee} = incident; + unscheduledIncidents.push({ + reservation, + lockEntry: doorLockEntry, + chargePrice, + timeIntervalsToCharge, + totalChargeFee, + }); + break; + } } - } - + }); }); insertUnscheduledIncidents(unscheduledIncidents) diff --git a/services/officeRnD/bookings.js b/services/officeRnD/bookings.js index db98a88..51607cb 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 } = require('../../constants/constants'); +const { officeRnDAPIErrors, MAX_BACK_TO_BACK_DIFFERENCE } = require('../../constants/constants'); const fetchAllBookings = () => { return new Promise((resolve, reject) => { @@ -57,7 +57,6 @@ const getAllFinishedBookings = () => { const getFirstNextBooking = (reservation) => { return new Promise ((resolve, reject) => { const { resourceId, start } = reservation; - const endOfTheDay = moment.utc(start).endOf('Day').toISOString(); const attributes = ['reservationId', 'memberId', 'resourceId', 'start', 'end', 'timezone']; const filters = { @@ -65,9 +64,6 @@ const getFirstNextBooking = (reservation) => { start: { [Op.gt]: start }, - end: { - [Op.lte]: endOfTheDay - }, resourceId, }; const order = [['start', 'ASC']]; @@ -91,14 +87,10 @@ const getFirstNextBooking = (reservation) => { const getFirstPreviousBooking = (reservation) => { return new Promise ((resolve, reject) => { const { resourceId, start } = reservation; - const startOfTheDay = moment.utc(start).startOf('Day').toISOString(); const attributes = ['reservationId', 'memberId', 'resourceId', 'start', 'end', 'timezone']; const filters = { canceled: false, - start: { - [Op.gte]: startOfTheDay - }, end: { [Op.lte]: start }, @@ -122,6 +114,36 @@ const getFirstPreviousBooking = (reservation) => { }); }; +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 = reservation.end; + + 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) => { return db.bookingReservation.findOrCreate({where: {...bookingReservation}, defaults: {...bookingReservation}}); }; @@ -132,4 +154,5 @@ module.exports = { getAllFinishedBookings, getFirstNextBooking, getFirstPreviousBooking, + getFirstReservationInBlock };