From 5d3653cd656a75ca6668d69712ec06aca2039212 Mon Sep 17 00:00:00 2001 From: Bilal Catic Date: Sun, 7 Jul 2019 00:28:39 +0200 Subject: [PATCH] make difference between door lock charges related to reservations and standalone incidents --- constants/constants.js | 6 +- ...y-column-to-the-unlockedIncidents-table.js | 14 ++ ...lumns-to-the-unscheduledIncidents-table.js | 27 +++ models/unlockedIncident.js | 1 + models/unscheduledIncident.js | 2 + services/doorLock/doorLock.js | 3 - services/integration/doorLockCharges.js | 182 ++++++++++-------- services/integration/reports.js | 11 +- 8 files changed, 162 insertions(+), 84 deletions(-) create mode 100644 migrations/20190705054346-add-door-lock-entry-column-to-the-unlockedIncidents-table.js create mode 100644 migrations/20190705065019-add-door-lock-entry-columns-to-the-unscheduledIncidents-table.js diff --git a/constants/constants.js b/constants/constants.js index 372674a..89ded66 100644 --- a/constants/constants.js +++ b/constants/constants.js @@ -61,8 +61,10 @@ const integrationServiceErrors = { const incidentType = { NOT_AN_INCIDENT: 1, - UNLOCKED_INCIDENT: 2, - UNSCHEDULED_INCIDENT: 3, + UNLOCKED_INCIDENT_RELATED_WITH_RESERVATION: 2, + UNSCHEDULED_INCIDENT_RELATED_WITH_RESERVATION: 3, + UNLOCKED_INCIDENT_STANDALONE: 4, + UNSCHEDULED_INCIDENT_STANDALONE: 5, }; const UI_TIMEZONE = process.env.UI_TIMEZONE || 'America/Los_Angeles'; diff --git a/migrations/20190705054346-add-door-lock-entry-column-to-the-unlockedIncidents-table.js b/migrations/20190705054346-add-door-lock-entry-column-to-the-unlockedIncidents-table.js new file mode 100644 index 0000000..d3da894 --- /dev/null +++ b/migrations/20190705054346-add-door-lock-entry-column-to-the-unlockedIncidents-table.js @@ -0,0 +1,14 @@ +'use strict'; + +module.exports = { + up: (queryInterface, Sequelize) => { + return queryInterface.addColumn('unlockedIncidents', 'unlockTimestamp', { + type: Sequelize.DATE, + after: 'bookingEnd' + }); + }, + + down: (queryInterface, Sequelize) => { + return queryInterface.removeColumn('unlockedIncidents', 'unlockTimestamp'); + } +}; diff --git a/migrations/20190705065019-add-door-lock-entry-columns-to-the-unscheduledIncidents-table.js b/migrations/20190705065019-add-door-lock-entry-columns-to-the-unscheduledIncidents-table.js new file mode 100644 index 0000000..b829082 --- /dev/null +++ b/migrations/20190705065019-add-door-lock-entry-columns-to-the-unscheduledIncidents-table.js @@ -0,0 +1,27 @@ +'use strict'; + +module.exports = { + up: (queryInterface, Sequelize) => { + return queryInterface.sequelize.transaction((t) => { + return Promise.all([ + queryInterface.addColumn('unscheduledIncidents', 'unlockTimestamp', { + type: Sequelize.DATE, + after: 'bookingEnd' + }), + queryInterface.addColumn('unscheduledIncidents', 'lockTimestamp', { + type: Sequelize.DATE, + after: 'unlockTimestamp' + }) + ]); + }); + }, + + down: (queryInterface, Sequelize) => { + return queryInterface.sequelize.transaction((t) => { + return Promise.all([ + queryInterface.removeColumn('unscheduledIncidents', 'lockTimestamp'), + queryInterface.removeColumn('unscheduledIncidents', 'unlockTimestamp') + ]); + }); + } +}; diff --git a/models/unlockedIncident.js b/models/unlockedIncident.js index 89c8c34..d77c126 100644 --- a/models/unlockedIncident.js +++ b/models/unlockedIncident.js @@ -21,6 +21,7 @@ module.exports = (sequelize, DataTypes) => { ] }, incidentLevelPrice: DataTypes.FLOAT, + unlockTimestamp: DataTypes.DATE, }, {}); unlockedIncident.associate = function(models) { // associations can be defined here diff --git a/models/unscheduledIncident.js b/models/unscheduledIncident.js index 1f781b9..c24ce79 100644 --- a/models/unscheduledIncident.js +++ b/models/unscheduledIncident.js @@ -17,6 +17,8 @@ module.exports = (sequelize, DataTypes) => { chargePrice: DataTypes.FLOAT, timeIntervalsToCharge: DataTypes.INTEGER, totalChargeFee: DataTypes.FLOAT, + unlockTimestamp: DataTypes.DATE, + lockTimestamp: DataTypes.DATE, }, {}); unscheduledIncident.associate = function(models) { // associations can be defined here diff --git a/services/doorLock/doorLock.js b/services/doorLock/doorLock.js index fdea6f7..b304797 100644 --- a/services/doorLock/doorLock.js +++ b/services/doorLock/doorLock.js @@ -188,8 +188,6 @@ const getUnlockEntryForReservation = (reservation, previousReservation) => { return new Promise((resolve, reject) => { const { memberId, resourceId } = reservation; - const attributes = ['memberName', 'event', 'timestamp', 'resourceId']; - const previousReservationEndMoment = previousReservation && previousReservation.end ? moment.utc(previousReservation.end) : null; const reservationStartMoment = moment.utc(reservation.start); @@ -214,7 +212,6 @@ const getUnlockEntryForReservation = (reservation, previousReservation) => { const order = [['timestamp', 'DESC']]; db.doorLockEvent.findAll({ - attributes, where: filters, order, }) diff --git a/services/integration/doorLockCharges.js b/services/integration/doorLockCharges.js index 0c70262..c1f9599 100644 --- a/services/integration/doorLockCharges.js +++ b/services/integration/doorLockCharges.js @@ -8,11 +8,11 @@ const { getUnlockEntryForReservation, getLockEntryForReservation, getEntriesBetw const { getAllFinishedBookings, getFirstPreviousBooking, getFirstNextBooking } = require('../officeRnD/bookings'); const getSortedIncidentsForMember = (memberId) => { - const attributes = ['bookingStart', 'incidentLevel', 'incidentLevelPrice']; + const attributes = ['bookingStart', 'incidentLevel', 'incidentLevelPrice', 'unlockTimestamp']; const filters = { memberId }; - const order = [['bookingStart', 'DESC']]; + const order = [['unlockTimestamp', 'DESC']]; return db.unlockedIncident.findAll({ attributes, @@ -24,9 +24,8 @@ const getSortedIncidentsForMember = (memberId) => { 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 { reservation, unlockTimestamp, lockTimestamp, memberId, resourceId, chargePrice, timeIntervalsToCharge, totalChargeFee } = incident; + const { reservationId, start, end } = reservation; const incidentForDB = { reservationId, @@ -34,11 +33,13 @@ const insertUnscheduledIncidents = (incidents) => { resourceId, bookingStart: start, bookingEnd: end, - doorLockEventTimestamp: timestamp, - doorLockEventType: event, + doorLockEventTimestamp: null, + doorLockEventType: null, chargePrice, timeIntervalsToCharge, totalChargeFee, + unlockTimestamp, + lockTimestamp, }; asyncJobs.push(db.unscheduledIncident.findOrCreate({ @@ -48,8 +49,8 @@ const insertUnscheduledIncidents = (incidents) => { resourceId, bookingStart: start, bookingEnd: end, - doorLockEventTimestamp: timestamp, - doorLockEventType: event + unlockTimestamp, + lockTimestamp, }, defaults: {...incidentForDB}, })); @@ -61,36 +62,50 @@ const insertUnscheduledIncidents = (incidents) => { const insertUnlockedIncidents = (incidents) => { const asyncJobs = []; incidents.forEach((incident) => { - const { reservationId, memberId, resourceId, bookingStart, bookingEnd } = incident; + const { reservation, memberId, resourceId, unlockTimestamp, incidentLevel, incidentLevelPrice } = incident; + const { reservationId, start, end} = reservation; + + const incidentForDB = { + reservationId, + memberId, + resourceId, + bookingStart: start, + bookingEnd: end, + unlockTimestamp, + incidentLevel, + incidentLevelPrice, + }; asyncJobs.push(db.unlockedIncident.findOrCreate({ where: { reservationId, memberId, resourceId, - bookingStart, - bookingEnd, + bookingStart: start, + bookingEnd: end, + unlockTimestamp, + incidentLevel, }, - defaults: {...incident}, + defaults: {...incidentForDB}, })); }); return Promise.all(asyncJobs); }; -const setUnlockedIncidentsLevel = (incidentReservations) => { +const setUnlockedIncidentsLevel = (incidents) => { return new Promise ((resolve, reject) => { - const sortingFunction = (reservationA, reservationB) => { - const sortCondition = moment.utc(reservationA.start).isBefore(moment.utc(reservationB.start)); + const sortingFunction = (incidentA, incidentB) => { + const sortCondition = moment.utc(incidentA.unlockTimestamp).isBefore(moment.utc(incidentB.unlockTimestamp)); return sortCondition ? -1 : 1; }; - incidentReservations.sort(sortingFunction); + incidents.sort(sortingFunction); const membersLastIncident = {}; - incidentReservations.forEach((reservation) => { - membersLastIncident[reservation.memberId] = { + incidents.forEach((incident) => { + membersLastIncident[incident.memberId] = { incidentLevel: null, incidentTimestamp: null, }; @@ -108,59 +123,58 @@ const setUnlockedIncidentsLevel = (incidentReservations) => { if (lastIncident) { membersLastIncident[lastIncident.memberId] = { incidentLevel: lastIncident.incidentLevel, - incidentTimestamp: lastIncident.bookingStart, + incidentTimestamp: lastIncident.unlockTimestamp, } } }); const incidentsWithLevel = []; - incidentReservations.forEach((reservation) => { - const memberLastIncident = membersLastIncident[reservation.memberId]; + incidents.forEach((incident) => { + const memberLastIncident = membersLastIncident[incident.memberId]; - const incident = { - reservationId: reservation.reservationId, - memberId: reservation.memberId, - resourceId: reservation.resourceId, - bookingStart: reservation.start, - bookingEnd: reservation.end, + const formattedIncident = { + reservation: incident.reservation, + memberId: incident.memberId, + resourceId: incident.resourceId, incidentLevel: undefined, incidentLevelPrice: undefined, + unlockTimestamp: incident.unlockTimestamp, }; if (!memberLastIncident.incidentLevel) { - incident.incidentLevel = unlockedIncidentLevelsPrices.UNLOCKED_0.title; - incident.incidentLevelPrice = unlockedIncidentLevelsPrices.UNLOCKED_0.price; + formattedIncident.incidentLevel = unlockedIncidentLevelsPrices.UNLOCKED_0.title; + formattedIncident.incidentLevelPrice = unlockedIncidentLevelsPrices.UNLOCKED_0.price; } else { const lastIncidentTime = moment.utc(memberLastIncident.incidentTimestamp).startOf('month'); - const currentIncidentTime = moment.utc(reservation.start).startOf('month'); + const currentIncidentTime = moment.utc(incident.unlockTimestamp).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; + formattedIncident.incidentLevel = unlockedIncidentLevelsPrices.UNLOCKED_0.title; + formattedIncident.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; + formattedIncident.incidentLevel = memberLastIncident.incidentLevel; + formattedIncident.incidentLevelPrice = unlockedIncidentLevelsPrices[formattedIncident.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 + formattedIncident.incidentLevel = unlockedIncidentLevelsPrices[key].title; + formattedIncident.incidentLevelPrice = unlockedIncidentLevelsPrices[key].price } }); } } } - memberLastIncident.incidentLevel = incident.incidentLevel; - memberLastIncident.incidentTimestamp = incident.bookingStart; + memberLastIncident.incidentLevel = formattedIncident.incidentLevel; + memberLastIncident.incidentTimestamp = formattedIncident.unlockTimestamp; - incidentsWithLevel.push(incident); + incidentsWithLevel.push(formattedIncident); }); resolve(incidentsWithLevel); @@ -318,17 +332,18 @@ const getIncidentData = (reservation) => { if (!pairUnlockEntry){ pairUnlockEntry = entry; }else{ - const virtualReservation = { - reservationId: '', - start: pairUnlockEntry.timestamp, - end: pairUnlockEntry.timestamp, - memberId: pairUnlockEntry.memberId, - resourceId, + const emptyReservation = { + reservationId: null, + start: null, + end: null, }; incidents.push({ - incidentType: incidentType.UNLOCKED_INCIDENT, - reservation: virtualReservation, + incidentType: incidentType.UNLOCKED_INCIDENT_STANDALONE, + reservation: emptyReservation, + unlockTimestamp: pairUnlockEntry.timestamp, + memberId: pairUnlockEntry.memberId, + resourceId, }); pairLockEntry = null; @@ -338,12 +353,10 @@ const getIncidentData = (reservation) => { case doorLockEvents.USER_LOCKED: if (pairUnlockEntry && !pairLockEntry){ pairLockEntry = entry; - const virtualReservation = { - reservationId: '', - start: pairUnlockEntry.timestamp, - end: pairLockEntry.timestamp, - memberId: pairUnlockEntry.memberId, - resourceId, + const emptyReservation = { + reservationId: null, + start: null, + end: null, }; const unlockMoment = moment.utc(pairUnlockEntry.timestamp); const lockMoment = moment.utc(pairLockEntry.timestamp); @@ -352,9 +365,12 @@ const getIncidentData = (reservation) => { const totalChargeFee = timeIntervalsToCharge * UNSCHEDULED_CHARGE_PRICE; if (timeIntervalsToCharge > 0){ incidents.push({ - incidentType: incidentType.UNSCHEDULED_INCIDENT, - reservation: virtualReservation, - doorLockEntry: pairLockEntry, + incidentType: incidentType.UNSCHEDULED_INCIDENT_STANDALONE, + reservation: emptyReservation, + unlockTimestamp: pairUnlockEntry.timestamp, + lockTimestamp: pairLockEntry.timestamp, + memberId: pairUnlockEntry.memberId, + resourceId, chargePrice: UNSCHEDULED_CHARGE_PRICE, timeIntervalsToCharge, totalChargeFee, @@ -384,9 +400,12 @@ const getIncidentData = (reservation) => { // 1. Check if member entered before reservation start time if (unlockEntry && chargeBefore && !previousReservationIsBackToBack) { incidents.push({ - incidentType: incidentType.UNSCHEDULED_INCIDENT, + incidentType: incidentType.UNSCHEDULED_INCIDENT_RELATED_WITH_RESERVATION, reservation, - doorLockEntry: unlockEntry, + unlockTimestamp: unlockEntry.timestamp, + lockTimestamp: null, + memberId: reservation.memberId, + resourceId: reservation.resourceId, chargePrice: UNSCHEDULED_CHARGE_PRICE, timeIntervalsToCharge: timeIntervalsToChargeBefore, totalChargeFee: totalChargeFeeBefore, @@ -396,9 +415,12 @@ const getIncidentData = (reservation) => { // 2. Check if member left after reservation end time if (lockEntry && chargeAfter && !nextReservationIsBackToBack) { incidents.push({ - incidentType: incidentType.UNSCHEDULED_INCIDENT, + incidentType: incidentType.UNSCHEDULED_INCIDENT_RELATED_WITH_RESERVATION, reservation, - doorLockEntry: lockEntry, + unlockTimestamp: null, + lockTimestamp: lockEntry.timestamp, + memberId: reservation.memberId, + resourceId: reservation.resourceId, chargePrice: UNSCHEDULED_CHARGE_PRICE, timeIntervalsToCharge: timeIntervalsToChargeAfter, totalChargeFee: totalChargeFeeAfter, @@ -407,10 +429,20 @@ const getIncidentData = (reservation) => { // 3. Check if member forgot to lock the door if (!lockEntry && !nextReservationIsBackToBack){ + const emptyReservation = { + reservationId: null, + start: null, + end: null, + }; + if (unlockEntry){ + incidents.push({ - incidentType: incidentType.UNLOCKED_INCIDENT, - reservation, + incidentType: incidentType.UNLOCKED_INCIDENT_RELATED_WITH_RESERVATION, + unlockTimestamp: unlockEntry.timestamp, + memberId: reservation.memberId, + resourceId: unlockEntry.resourceId, + reservation: reservation && reservation.dataValues ? reservation.dataValues : emptyReservation, }); } else { // No lock entry, no unlock entry and no reservation after this one @@ -428,8 +460,11 @@ const getIncidentData = (reservation) => { .then((lastEntry) => { if (lastEntry && lastEntry.event === doorLockEvents.USER_UNLOCKED){ incidents.push({ - incidentType: incidentType.UNLOCKED_INCIDENT, - reservation, + incidentType: incidentType.UNLOCKED_INCIDENT_RELATED_WITH_RESERVATION, + unlockTimestamp: lastEntry.timestamp, + memberId: lastEntry.memberId, + resourceId: lastEntry.resourceId, + reservation: reservation && reservation.dataValues ? reservation.dataValues : emptyReservation, }); } }) @@ -469,18 +504,13 @@ const calculateDoorLockCharges = () => { console.log('Error checking incident : ', incident.error); } else if (incident.incidentType) { switch (incident.incidentType) { - case incidentType.UNLOCKED_INCIDENT: - unlockedIncidents.push(incident.reservation); + case incidentType.UNLOCKED_INCIDENT_RELATED_WITH_RESERVATION: + case incidentType.UNLOCKED_INCIDENT_STANDALONE: + unlockedIncidents.push(incident); break; - case incidentType.UNSCHEDULED_INCIDENT: - const {reservation, doorLockEntry, chargePrice, timeIntervalsToCharge, totalChargeFee} = incident; - unscheduledIncidents.push({ - reservation, - lockEntry: doorLockEntry, - chargePrice, - timeIntervalsToCharge, - totalChargeFee, - }); + case incidentType.UNSCHEDULED_INCIDENT_RELATED_WITH_RESERVATION: + case incidentType.UNSCHEDULED_INCIDENT_STANDALONE: + unscheduledIncidents.push(incident); break; } } diff --git a/services/integration/reports.js b/services/integration/reports.js index eee8f68..8cb5e87 100644 --- a/services/integration/reports.js +++ b/services/integration/reports.js @@ -11,7 +11,7 @@ const { fetchAllMembers } = require('../officeRnD/members'); const { fetchOffices, fetchResources } = require('../officeRnD/resources'); const getUnlockedIncidents = (startDate, endDate, memberId) => { - const attributes = ['id', 'memberId', 'resourceId', 'bookingStart', 'bookingEnd', 'incidentLevel', 'incidentLevelPrice']; + const attributes = ['id', 'reservationId', 'memberId', 'resourceId', 'bookingStart', 'bookingEnd', 'incidentLevel', 'incidentLevelPrice']; const filters = {}; @@ -40,6 +40,7 @@ const getUnlockedIncidents = (startDate, endDate, memberId) => { const getUnscheduledIncidents = (startDate, endDate, memberId) => { const attributes = [ 'id', + 'reservationId', 'memberId', 'resourceId', 'bookingStart', @@ -114,6 +115,8 @@ const getAllDoorLockIncidents = (dateRange, memberId) => { const allIncidents = []; unlockedIncidents.forEach((unlockedIncident) => { + const incidentTypeNumber = unlockedIncident.reservationId.length > 0 ? + incidentType.UNLOCKED_INCIDENT_RELATED_WITH_RESERVATION : incidentType.UNLOCKED_INCIDENT_STANDALONE; allIncidents.push({ incidentId: unlockedIncident.id, memberId: unlockedIncident.memberId, @@ -122,13 +125,15 @@ const getAllDoorLockIncidents = (dateRange, memberId) => { officeName: officesMap[resourcesMap[unlockedIncident.resourceId].officeId].officeName, bookingStart: formatTime(unlockedIncident.bookingStart), bookingEnd: formatTime(unlockedIncident.bookingEnd), - incidentType: incidentType.UNLOCKED_INCIDENT, + incidentType: incidentTypeNumber, incidentLevel: unlockedIncident.incidentLevel, incidentPrice: unlockedIncident.incidentLevelPrice, }); }); unscheduledIncidents.forEach((unscheduledIncident) => { + const incidentTypeNumber = unscheduledIncident.reservationId.length > 0 ? + incidentType.UNSCHEDULED_INCIDENT_RELATED_WITH_RESERVATION : incidentType.UNSCHEDULED_INCIDENT_STANDALONE; allIncidents.push({ incidentId: unscheduledIncident.id, memberId: unscheduledIncident.memberId, @@ -137,7 +142,7 @@ const getAllDoorLockIncidents = (dateRange, memberId) => { officeName: officesMap[resourcesMap[unscheduledIncident.resourceId].officeId].officeName, bookingStart: formatTime(unscheduledIncident.bookingStart), bookingEnd: formatTime(unscheduledIncident.bookingEnd), - incidentType: incidentType.UNSCHEDULED_INCIDENT, + incidentType: incidentTypeNumber, timeIntervalsToCharge: unscheduledIncident.timeIntervalsToCharge, chargePrice: unscheduledIncident.chargePrice, totalChargeFee: unscheduledIncident.totalChargeFee,