Fix problems with door unlock charges
This commit is contained in:
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user