Calculate door lock charges
This commit is contained in:
502
services/integration/doorLockCharges.js
Normal file
502
services/integration/doorLockCharges.js
Normal file
@@ -0,0 +1,502 @@
|
||||
'use strict';
|
||||
|
||||
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 getSortedIncidentsForMember = (memberId) => {
|
||||
const attributes = ['bookingStart', 'incidentLevel', 'incidentLevelPrice'];
|
||||
const filters = {
|
||||
memberId
|
||||
};
|
||||
const order = [['bookingStart', 'DESC']];
|
||||
|
||||
return db.unlockedIncident.findAll({
|
||||
attributes,
|
||||
where: filters,
|
||||
order,
|
||||
})
|
||||
};
|
||||
|
||||
const createUnlockedIncident = (reservation) => {
|
||||
return new Promise((resolve, reject) => {
|
||||
const { reservationId, memberId, resourceId, start, end } = reservation;
|
||||
|
||||
getLastIncidentForMember(memberId)
|
||||
.then(incidents => {
|
||||
const lastIncident = incidents && incidents[0] ? incidents[0] : undefined;
|
||||
|
||||
const incident = {
|
||||
reservationId,
|
||||
memberId,
|
||||
resourceId,
|
||||
bookingStart: start,
|
||||
bookingEnd: end,
|
||||
incidentLevel: null,
|
||||
incidentLevelPrice: null,
|
||||
};
|
||||
|
||||
console.log('=> UNLOCKED INCIDENT');
|
||||
console.log('\tMember : ', memberId);
|
||||
console.log('\tStart : ', start);
|
||||
console.log('\tEnd : ', end);
|
||||
console.log('\tMore details : ');
|
||||
|
||||
/*
|
||||
if (lastIncident){
|
||||
const lastIncidentLevel = lastIncident.incidentLevel;
|
||||
const lastIncidentBeginningOfTheMonth = moment(lastIncident.bookingStart).startOf('month');
|
||||
const beginningOfTheMonth = moment.utc().startOf('month');
|
||||
|
||||
const timePassedFromLastIncident = Math.abs(beginningOfTheMonth.diff(lastIncidentBeginningOfTheMonth, 'months'));
|
||||
|
||||
if (timePassedFromLastIncident >= 6){
|
||||
console.log('\t\t-> This is first incident for this member in last 6 months');
|
||||
incident.incidentLevel = unlockedIncidentLevelsPrices.UNLOCKED_0.title;
|
||||
incident.incidentLevelPrice = unlockedIncidentLevelsPrices.UNLOCKED_0.price;
|
||||
} else {
|
||||
console.log('\t\t-> This member had incident(s) in past 6 months !!!');
|
||||
incident.incidentLevel = lastIncidentLevel;
|
||||
incident.incidentLevelPrice = unlockedIncidentLevelsPrices[lastIncidentLevel].price;
|
||||
}
|
||||
console.log('\t\tLast incident details : ');
|
||||
console.log('\t\tStart : ', lastIncident.bookingStart);
|
||||
console.log('\t\tCalculated diff : ', timePassedFromLastIncident);
|
||||
console.log('\t\t------------------');
|
||||
console.log('\tNew incident level : ', incident.incidentLevel);
|
||||
} else {
|
||||
console.log('\t\tThis is first incident for this member, EVER !');
|
||||
incident.incidentLevel = unlockedIncidentLevelsPrices.UNLOCKED_0.title;
|
||||
incident.incidentLevelPrice = unlockedIncidentLevelsPrices.UNLOCKED_0.price;
|
||||
}
|
||||
*/
|
||||
|
||||
db.unlockedIncident.findOrCreate({
|
||||
where: {
|
||||
reservationId,
|
||||
memberId,
|
||||
resourceId,
|
||||
bookingStart: start,
|
||||
bookingEnd: end,
|
||||
},
|
||||
defaults: {
|
||||
...incident
|
||||
}
|
||||
})
|
||||
.then(()=>resolve())
|
||||
.catch((error)=>reject(error));
|
||||
})
|
||||
.catch((error) => {
|
||||
reject(error);
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
const createUnscheduledUseIncident = (reservation, doorLockEntry) => {
|
||||
return new Promise((resolve, reject) => {
|
||||
const timeResolution = parseInt(process.env.UNSCHEDULED_USE_TIME_RESOLUTION);
|
||||
const chargePrice = parseFloat(process.env.UNSCHEDULED_USE_CHARGE_FEE);
|
||||
|
||||
const reservationEndTime = moment(reservation.end);
|
||||
const lockedTime = moment(doorLockEntry.timestamp);
|
||||
const timeDifference = Math.abs(reservationEndTime.diff(lockedTime, 'minutes'));
|
||||
|
||||
const timeIntervalsToCharge = Math.floor(timeDifference / timeResolution);
|
||||
const totalChargeFee = timeIntervalsToCharge * chargePrice;
|
||||
|
||||
if (timeIntervalsToCharge > 0){
|
||||
const incident = {
|
||||
reservationId: reservation.reservationId,
|
||||
memberId: reservation.memberId,
|
||||
resourceId: reservation.resourceId,
|
||||
bookingStart: reservation.start,
|
||||
bookingEnd: reservation.end,
|
||||
doorLockEventTimestamp: doorLockEntry.timestamp,
|
||||
doorLockEventType: doorLockEntry.event,
|
||||
chargePrice,
|
||||
timeIntervalsToCharge,
|
||||
totalChargeFee,
|
||||
};
|
||||
|
||||
db.unscheduledIncident.findOrCreate({where: {...incident}, defaults: {...incident}})
|
||||
.then(()=>resolve())
|
||||
.catch((error)=>reject(error));
|
||||
}else{
|
||||
resolve();
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
const createDoorLockIncident = (reservation, doorLockEntry) => {
|
||||
return new Promise((resolve, reject) => {
|
||||
if (!doorLockEntry){
|
||||
// Check if there is unlock entry for this reservation
|
||||
getUnlockEntryForReservation(reservation)
|
||||
.then((unlockEntry) => {
|
||||
if (!unlockEntry){
|
||||
// check if there is back-to-back booking before current one
|
||||
getFirstPreviousBooking(reservation)
|
||||
.then((previousReservation) => {
|
||||
if (previousReservation){
|
||||
const previousReservationEnd = moment(previousReservation.end);
|
||||
const currentReservationStart = moment(reservation.start);
|
||||
const timeDifference = Math.abs(currentReservationStart.diff(previousReservationEnd, 'minutes'));
|
||||
|
||||
const maxBackToBackDifference = parseInt(process.env.MAX_BACK_TO_BACK_DIFFERENCE) || 0;
|
||||
if (timeDifference <= maxBackToBackDifference) {
|
||||
createUnlockedIncident(reservation)
|
||||
.then(() => resolve())
|
||||
.catch((error) => reject(error));
|
||||
}else{
|
||||
resolve();
|
||||
}
|
||||
}else{
|
||||
resolve();
|
||||
}
|
||||
})
|
||||
.catch((error)=>reject(error));
|
||||
}else {
|
||||
createUnlockedIncident(reservation)
|
||||
.then(()=>resolve())
|
||||
.catch((error)=>reject(error));
|
||||
}
|
||||
})
|
||||
.catch((error) => {
|
||||
reject(error);
|
||||
});
|
||||
}else{
|
||||
createUnscheduledUseIncident(reservation, doorLockEntry)
|
||||
.then(()=>resolve())
|
||||
.catch((error) => reject(error));
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
const insertUnscheduledIncidents = (incidents) => {
|
||||
const asyncJobs = [];
|
||||
incidents.forEach((incident) => {
|
||||
const { reservation, lockEntry, chargePrice, timeIntervalsToCharge, totalChargeFee } = incident;
|
||||
const { reservationId, memberId, resourceId, start, end } = reservation;
|
||||
const { timestamp, event } = lockEntry;
|
||||
|
||||
const incidentForDB = {
|
||||
reservationId,
|
||||
memberId,
|
||||
resourceId,
|
||||
bookingStart: start,
|
||||
bookingEnd: end,
|
||||
doorLockEventTimestamp: timestamp,
|
||||
doorLockEventType: event,
|
||||
chargePrice,
|
||||
timeIntervalsToCharge,
|
||||
totalChargeFee,
|
||||
};
|
||||
|
||||
asyncJobs.push(db.unscheduledIncident.findOrCreate({
|
||||
where: {
|
||||
reservationId,
|
||||
memberId,
|
||||
resourceId,
|
||||
bookingStart: start,
|
||||
bookingEnd: end,
|
||||
doorLockEventTimestamp: timestamp,
|
||||
doorLockEventType: event
|
||||
},
|
||||
defaults: {...incidentForDB},
|
||||
}));
|
||||
});
|
||||
|
||||
return Promise.all(asyncJobs);
|
||||
};
|
||||
|
||||
const insertUnlockedIncidents = (incidents) => {
|
||||
const asyncJobs = [];
|
||||
incidents.forEach((incident) => {
|
||||
const { reservationId, memberId, resourceId, bookingStart, bookingEnd } = incident;
|
||||
|
||||
asyncJobs.push(db.unlockedIncident.findOrCreate({
|
||||
where: {
|
||||
reservationId,
|
||||
memberId,
|
||||
resourceId,
|
||||
bookingStart,
|
||||
bookingEnd,
|
||||
},
|
||||
defaults: {...incident},
|
||||
}));
|
||||
});
|
||||
|
||||
return Promise.all(asyncJobs);
|
||||
};
|
||||
|
||||
const setUnlockedIncidentsLevel = (incidentReservations) => {
|
||||
return new Promise ((resolve, reject) => {
|
||||
const sortingFunction = (reservationA, reservationB) => {
|
||||
const sortCondition = moment.utc(reservationA.start).isBefore(moment.utc(reservationB.start));
|
||||
return sortCondition ? -1 : 1;
|
||||
};
|
||||
|
||||
incidentReservations.sort(sortingFunction);
|
||||
|
||||
const membersLastIncident = {};
|
||||
|
||||
incidentReservations.forEach((reservation) => {
|
||||
membersLastIncident[reservation.memberId] = {
|
||||
incidentLevel: null,
|
||||
incidentTimestamp: null,
|
||||
};
|
||||
});
|
||||
|
||||
const asyncJobs = [];
|
||||
Object.keys(membersLastIncident).forEach((memberId) => {
|
||||
asyncJobs.push(getSortedIncidentsForMember(memberId));
|
||||
});
|
||||
|
||||
Promise.all(asyncJobs)
|
||||
.then((results) => {
|
||||
results.forEach((result) => {
|
||||
const lastIncident = result && result[0] ? result[0] : null;
|
||||
if (lastIncident) {
|
||||
membersLastIncident[lastIncident.memberId] = {
|
||||
incidentLevel: lastIncident.incidentLevel,
|
||||
incidentTimestamp: lastIncident.bookingStart,
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
const incidentsWithLevel = [];
|
||||
|
||||
incidentReservations.forEach((reservation) => {
|
||||
const memberLastIncident = membersLastIncident[reservation.memberId];
|
||||
|
||||
const incident = {
|
||||
reservationId: reservation.reservationId,
|
||||
memberId: reservation.memberId,
|
||||
resourceId: reservation.resourceId,
|
||||
bookingStart: reservation.start,
|
||||
bookingEnd: reservation.end,
|
||||
incidentLevel: undefined,
|
||||
incidentLevelPrice: undefined,
|
||||
};
|
||||
|
||||
if (!memberLastIncident.incidentLevel) {
|
||||
incident.incidentLevel = unlockedIncidentLevelsPrices.UNLOCKED_0.title;
|
||||
incident.incidentLevelPrice = unlockedIncidentLevelsPrices.UNLOCKED_0.price;
|
||||
} else {
|
||||
const lastIncidentTime = moment.utc(memberLastIncident.incidentTimestamp).startOf('month');
|
||||
const currentIncidentTime = moment.utc(reservation.start).startOf('month');
|
||||
const timeDiff = Math.abs(lastIncidentTime.diff(currentIncidentTime, 'months'));
|
||||
|
||||
if (timeDiff >= (parseInt(process.env.UNLOCK_STREAK_REPAIR_AFTER) || 6)){
|
||||
incident.incidentLevel = unlockedIncidentLevelsPrices.UNLOCKED_0.title;
|
||||
incident.incidentLevelPrice = unlockedIncidentLevelsPrices.UNLOCKED_0.price;
|
||||
} else {
|
||||
const lastIncidentLevelId = unlockedIncidentLevelsPrices[memberLastIncident.incidentLevel].id;
|
||||
const maxId = 5;
|
||||
|
||||
if ((lastIncidentLevelId && (lastIncidentLevelId >= maxId)) || (timeDiff === 0)){
|
||||
incident.incidentLevel = memberLastIncident.incidentLevel;
|
||||
incident.incidentLevelPrice = unlockedIncidentLevelsPrices[incident.incidentLevel].price;
|
||||
} else {
|
||||
const nextId = lastIncidentLevelId + 1;
|
||||
Object.keys(unlockedIncidentLevelsPrices).forEach((key) => {
|
||||
if (unlockedIncidentLevelsPrices[key].id === nextId){
|
||||
incident.incidentLevel = unlockedIncidentLevelsPrices[key].title;
|
||||
incident.incidentLevelPrice = unlockedIncidentLevelsPrices[key].price
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
memberLastIncident.incidentLevel = incident.incidentLevel;
|
||||
memberLastIncident.incidentTimestamp = incident.bookingStart;
|
||||
|
||||
incidentsWithLevel.push(incident);
|
||||
});
|
||||
|
||||
resolve(incidentsWithLevel);
|
||||
})
|
||||
.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;
|
||||
|
||||
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(reservation.end);
|
||||
const secondReservationStart = moment(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);
|
||||
const chargePrice = parseFloat(process.env.UNSCHEDULED_USE_CHARGE_FEE);
|
||||
|
||||
const reservationEndTime = moment(reservation.end);
|
||||
const lockedTime = moment(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(previousReservation.end);
|
||||
const currentReservationStart = moment(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,
|
||||
});
|
||||
});
|
||||
|
||||
})
|
||||
.catch((error) => {
|
||||
console.log('Error finding first next booking', error);
|
||||
resolve({
|
||||
error,
|
||||
});
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
const calculateDoorLockCharges = () => {
|
||||
getAllFinishedBookings()
|
||||
.then((reservations) => {
|
||||
const unlockedIncidents = [];
|
||||
const unscheduledIncidents = [];
|
||||
|
||||
const asyncCheckForIncidents = [];
|
||||
|
||||
reservations.forEach((reservation) => {
|
||||
asyncCheckForIncidents.push(getIncidentData(reservation));
|
||||
});
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
insertUnscheduledIncidents(unscheduledIncidents)
|
||||
.catch((error) => console.log(error));
|
||||
|
||||
setUnlockedIncidentsLevel(unlockedIncidents)
|
||||
.then((completedUnlockedIncidents) => {
|
||||
insertUnlockedIncidents(completedUnlockedIncidents)
|
||||
.catch((error) => console.log(error));
|
||||
})
|
||||
.catch((error) => console.log(error));
|
||||
})
|
||||
.catch((error) => console.log(error));
|
||||
})
|
||||
.catch((error) => console.log(error));
|
||||
};
|
||||
|
||||
module.exports = {
|
||||
calculateDoorLockCharges
|
||||
};
|
||||
Reference in New Issue
Block a user