Fix problems with door unlock charges
This commit is contained in:
@@ -69,6 +69,10 @@ const UI_TIMEZONE = process.env.UI_TIMEZONE || 'America/Los_Angeles';
|
|||||||
|
|
||||||
const DEFAULT_DATE_FORMAT = 'YYYY-MM-DD';
|
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 = {
|
module.exports = {
|
||||||
VALID_CSV_HEADERS,
|
VALID_CSV_HEADERS,
|
||||||
USER_ENTRY_EVENT,
|
USER_ENTRY_EVENT,
|
||||||
@@ -82,4 +86,7 @@ module.exports = {
|
|||||||
incidentType,
|
incidentType,
|
||||||
UI_TIMEZONE,
|
UI_TIMEZONE,
|
||||||
DEFAULT_DATE_FORMAT,
|
DEFAULT_DATE_FORMAT,
|
||||||
|
MAX_BACK_TO_BACK_DIFFERENCE,
|
||||||
|
UNSCHEDULED_TIME_RESOLUTION,
|
||||||
|
UNSCHEDULED_CHARGE_PRICE,
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -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
|
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_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_0=Price for unlocked door, first month
|
||||||
UNLOCK_1=Price for unlocked door, second month
|
UNLOCK_1=Price for unlocked door, second month
|
||||||
|
|||||||
@@ -183,57 +183,186 @@ const writeDoorLockEvent = (entry) => {
|
|||||||
return db.doorLockEvent.findOrCreate({where: {...entry}, defaults: {...entry}});
|
return db.doorLockEvent.findOrCreate({where: {...entry}, defaults: {...entry}});
|
||||||
};
|
};
|
||||||
|
|
||||||
const getUnlockEntryForReservation = (reservation) => {
|
const getUnlockEntryForReservation = (reservation, previousReservation) => {
|
||||||
const { start, end, memberId, resourceId } = reservation;
|
return new Promise((resolve, reject) => {
|
||||||
|
const { memberId, resourceId } = reservation;
|
||||||
|
|
||||||
const attributes = ['memberName', 'event', 'timestamp'];
|
const attributes = ['memberName', 'event', 'timestamp', 'resourceId'];
|
||||||
const earliestUnlock = parseInt(process.env.EARLIEST_UNLOCK) || 0;
|
|
||||||
const fromTimestamp = moment(start).subtract(earliestUnlock).toISOString();
|
|
||||||
const toTimestamp = end;
|
|
||||||
|
|
||||||
const filters = {
|
const fromTimestamp = previousReservation && previousReservation.end ?
|
||||||
memberId,
|
previousReservation.end : moment.utc(reservation.start).tz(UI_TIMEZONE).startOf('Day').toISOString();
|
||||||
timestamp: {
|
const toTimestamp = reservation.end;
|
||||||
[Op.and]: [
|
|
||||||
{[Op.gt]: fromTimestamp},
|
|
||||||
{[Op.lte]: toTimestamp}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
event: doorLockEvents.USER_UNLOCKED,
|
|
||||||
resourceId,
|
|
||||||
};
|
|
||||||
|
|
||||||
return db.doorLockEvent.findOne({
|
const filters = {
|
||||||
attributes,
|
memberId,
|
||||||
where: filters,
|
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 getLockEntryForReservation = (reservation, nextReservation) => {
|
||||||
const attributes = ['memberName', 'event', 'timestamp'];
|
return new Promise((resolve, reject) => {
|
||||||
|
const { memberId, resourceId } = reservation;
|
||||||
|
|
||||||
const filters = {
|
const attributes = ['memberName', 'event', 'timestamp'];
|
||||||
memberId,
|
|
||||||
timestamp: {
|
const fromTimestamp = reservation.start;
|
||||||
[Op.and]: [
|
const toTimestamp = nextReservation && nextReservation.start ?
|
||||||
{[Op.gt]: fromTimestamp},
|
nextReservation.start : moment.utc(reservation.start).tz(UI_TIMEZONE).endOf('Day').toISOString();
|
||||||
{[Op.lte]: toTimestamp}
|
|
||||||
|
const filters = {
|
||||||
|
memberId,
|
||||||
|
timestamp: {
|
||||||
|
[Op.and]: [
|
||||||
|
{[Op.gt]: fromTimestamp},
|
||||||
|
{[Op.lte]: toTimestamp}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
event: doorLockEvents.USER_LOCKED,
|
resourceId,
|
||||||
resourceId,
|
};
|
||||||
};
|
|
||||||
|
|
||||||
return db.doorLockEvent.findOne({
|
const order = [['timestamp', 'ASC']];
|
||||||
attributes,
|
|
||||||
where: filters
|
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 = {
|
module.exports = {
|
||||||
parseDoorLockDataFile,
|
parseDoorLockDataFile,
|
||||||
writeDoorLockEvent,
|
writeDoorLockEvent,
|
||||||
getRelatedDoorLockEntries,
|
|
||||||
getUnlockEntryForReservation,
|
getUnlockEntryForReservation,
|
||||||
|
getLockEntryForReservation,
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -3,9 +3,9 @@
|
|||||||
const moment = require('moment-timezone');
|
const moment = require('moment-timezone');
|
||||||
const db = require('../../models/index');
|
const db = require('../../models/index');
|
||||||
|
|
||||||
const { incidentType, unlockedIncidentLevelsPrices } = require('../../constants/constants');
|
const { incidentType, unlockedIncidentLevelsPrices, UNSCHEDULED_CHARGE_PRICE, MAX_BACK_TO_BACK_DIFFERENCE, UNSCHEDULED_TIME_RESOLUTION } = require('../../constants/constants');
|
||||||
const { getUnlockEntryForReservation, getRelatedDoorLockEntries } = require('../doorLock/doorLock');
|
const { getUnlockEntryForReservation, getLockEntryForReservation } = require('../doorLock/doorLock');
|
||||||
const { getFirstPreviousBooking, getFirstNextBooking, getAllFinishedBookings } = require('../officeRnD/bookings');
|
const { getAllFinishedBookings, getFirstReservationInBlock, getFirstPreviousBooking, getFirstNextBooking } = require('../officeRnD/bookings');
|
||||||
|
|
||||||
const getSortedIncidentsForMember = (memberId) => {
|
const getSortedIncidentsForMember = (memberId) => {
|
||||||
const attributes = ['bookingStart', 'incidentLevel', 'incidentLevelPrice'];
|
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) => {
|
const getIncidentData = (reservation) => {
|
||||||
return new Promise ((resolve, reject) => {
|
return new Promise ((resolve, reject) => {
|
||||||
getFirstNextBooking(reservation)
|
analyseReservation(reservation)
|
||||||
.then(nextReservation => {
|
.then((result) => {
|
||||||
const endOfTheDay = moment.tz(reservation.end, reservation.timezone).endOf('Day').toISOString();
|
const {
|
||||||
let doorLockEntriesEndTime = nextReservation ? nextReservation.start : endOfTheDay;
|
currentReservation,
|
||||||
|
previousReservation,
|
||||||
|
nextReservation,
|
||||||
|
unlockEntry,
|
||||||
|
lockEntry,
|
||||||
|
previousReservationIsBackToBack,
|
||||||
|
nextReservationIsBackToBack,
|
||||||
|
timeDifferenceFromPreviousReservation,
|
||||||
|
timeDifferenceBeforeNextReservation,
|
||||||
|
timeDifferenceFromUnlockEntry,
|
||||||
|
timeDifferenceFromLockEntry,
|
||||||
|
chargeBefore,
|
||||||
|
chargeAfter,
|
||||||
|
totalChargeFeeBefore,
|
||||||
|
totalChargeFeeAfter,
|
||||||
|
timeIntervalsToChargeBefore,
|
||||||
|
timeIntervalsToChargeAfter,
|
||||||
|
} = result;
|
||||||
|
const incidents = [];
|
||||||
|
|
||||||
if (nextReservation){
|
// 1. Check if member entered before reservation start time
|
||||||
// Check if next reservations is immediately after (back to back reservation)
|
if (unlockEntry && chargeBefore && !previousReservationIsBackToBack) {
|
||||||
// If yes, then there is no need to check door lock entries related to this booking
|
incidents.push({
|
||||||
const firstReservationEnd = moment.utc(reservation.end);
|
incidentType: incidentType.UNSCHEDULED_INCIDENT,
|
||||||
const secondReservationStart = moment.utc(nextReservation.start);
|
reservation,
|
||||||
const timeDifference = Math.abs(secondReservationStart.diff(firstReservationEnd, 'minutes'));
|
doorLockEntry: unlockEntry,
|
||||||
|
chargePrice: UNSCHEDULED_CHARGE_PRICE,
|
||||||
const maxBackToBackDifference = parseInt(process.env.MAX_BACK_TO_BACK_DIFFERENCE) || 0;
|
timeIntervalsToCharge: timeIntervalsToChargeBefore,
|
||||||
if (timeDifference <= maxBackToBackDifference){
|
totalChargeFee: totalChargeFeeBefore,
|
||||||
// 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,
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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) => {
|
.catch((error) => reject(error));
|
||||||
console.log('Error finding first next booking', error);
|
|
||||||
resolve({
|
|
||||||
error,
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -304,28 +378,29 @@ const calculateDoorLockCharges = () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
Promise.all(asyncCheckForIncidents)
|
Promise.all(asyncCheckForIncidents)
|
||||||
.then((results) => {
|
.then((allReservationsIncidents) => {
|
||||||
results.forEach((result) => {
|
allReservationsIncidents.forEach((reservationIncidents) => {
|
||||||
if (result.error){
|
reservationIncidents.forEach((incident) => {
|
||||||
console.log('Error checking incident : ', result.error);
|
if (incident.error) {
|
||||||
}else if(result.incidentType) {
|
console.log('Error checking incident : ', incident.error);
|
||||||
switch (result.incidentType) {
|
} else if (incident.incidentType) {
|
||||||
case incidentType.UNLOCKED_INCIDENT:
|
switch (incident.incidentType) {
|
||||||
unlockedIncidents.push(result.reservation);
|
case incidentType.UNLOCKED_INCIDENT:
|
||||||
break;
|
unlockedIncidents.push(incident.reservation);
|
||||||
case incidentType.UNSCHEDULED_INCIDENT:
|
break;
|
||||||
const { reservation, lockEntry, chargePrice, timeIntervalsToCharge, totalChargeFee } = result;
|
case incidentType.UNSCHEDULED_INCIDENT:
|
||||||
unscheduledIncidents.push({
|
const {reservation, doorLockEntry, chargePrice, timeIntervalsToCharge, totalChargeFee} = incident;
|
||||||
reservation,
|
unscheduledIncidents.push({
|
||||||
lockEntry,
|
reservation,
|
||||||
chargePrice,
|
lockEntry: doorLockEntry,
|
||||||
timeIntervalsToCharge,
|
chargePrice,
|
||||||
totalChargeFee,
|
timeIntervalsToCharge,
|
||||||
});
|
totalChargeFee,
|
||||||
break;
|
});
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
});
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
insertUnscheduledIncidents(unscheduledIncidents)
|
insertUnscheduledIncidents(unscheduledIncidents)
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ const moment = require('moment-timezone');
|
|||||||
const Op = require('sequelize').Op;
|
const Op = require('sequelize').Op;
|
||||||
|
|
||||||
const { API } = require('../../helpers/api');
|
const { API } = require('../../helpers/api');
|
||||||
const { officeRnDAPIErrors } = require('../../constants/constants');
|
const { officeRnDAPIErrors, MAX_BACK_TO_BACK_DIFFERENCE } = require('../../constants/constants');
|
||||||
|
|
||||||
const fetchAllBookings = () => {
|
const fetchAllBookings = () => {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
@@ -57,7 +57,6 @@ const getAllFinishedBookings = () => {
|
|||||||
const getFirstNextBooking = (reservation) => {
|
const getFirstNextBooking = (reservation) => {
|
||||||
return new Promise ((resolve, reject) => {
|
return new Promise ((resolve, reject) => {
|
||||||
const { resourceId, start } = reservation;
|
const { resourceId, start } = reservation;
|
||||||
const endOfTheDay = moment.utc(start).endOf('Day').toISOString();
|
|
||||||
|
|
||||||
const attributes = ['reservationId', 'memberId', 'resourceId', 'start', 'end', 'timezone'];
|
const attributes = ['reservationId', 'memberId', 'resourceId', 'start', 'end', 'timezone'];
|
||||||
const filters = {
|
const filters = {
|
||||||
@@ -65,9 +64,6 @@ const getFirstNextBooking = (reservation) => {
|
|||||||
start: {
|
start: {
|
||||||
[Op.gt]: start
|
[Op.gt]: start
|
||||||
},
|
},
|
||||||
end: {
|
|
||||||
[Op.lte]: endOfTheDay
|
|
||||||
},
|
|
||||||
resourceId,
|
resourceId,
|
||||||
};
|
};
|
||||||
const order = [['start', 'ASC']];
|
const order = [['start', 'ASC']];
|
||||||
@@ -91,14 +87,10 @@ const getFirstNextBooking = (reservation) => {
|
|||||||
const getFirstPreviousBooking = (reservation) => {
|
const getFirstPreviousBooking = (reservation) => {
|
||||||
return new Promise ((resolve, reject) => {
|
return new Promise ((resolve, reject) => {
|
||||||
const { resourceId, start } = reservation;
|
const { resourceId, start } = reservation;
|
||||||
const startOfTheDay = moment.utc(start).startOf('Day').toISOString();
|
|
||||||
|
|
||||||
const attributes = ['reservationId', 'memberId', 'resourceId', 'start', 'end', 'timezone'];
|
const attributes = ['reservationId', 'memberId', 'resourceId', 'start', 'end', 'timezone'];
|
||||||
const filters = {
|
const filters = {
|
||||||
canceled: false,
|
canceled: false,
|
||||||
start: {
|
|
||||||
[Op.gte]: startOfTheDay
|
|
||||||
},
|
|
||||||
end: {
|
end: {
|
||||||
[Op.lte]: start
|
[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) => {
|
const writeBookingReservation = (bookingReservation) => {
|
||||||
return db.bookingReservation.findOrCreate({where: {...bookingReservation}, defaults: {...bookingReservation}});
|
return db.bookingReservation.findOrCreate({where: {...bookingReservation}, defaults: {...bookingReservation}});
|
||||||
};
|
};
|
||||||
@@ -132,4 +154,5 @@ module.exports = {
|
|||||||
getAllFinishedBookings,
|
getAllFinishedBookings,
|
||||||
getFirstNextBooking,
|
getFirstNextBooking,
|
||||||
getFirstPreviousBooking,
|
getFirstPreviousBooking,
|
||||||
|
getFirstReservationInBlock
|
||||||
};
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user