From 5d3653cd656a75ca6668d69712ec06aca2039212 Mon Sep 17 00:00:00 2001 From: Bilal Catic Date: Sun, 7 Jul 2019 00:28:39 +0200 Subject: [PATCH 1/6] 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, From bd513b75990fc9e779eab55f8611788930b58c6f Mon Sep 17 00:00:00 2001 From: Bilal Catic Date: Sun, 7 Jul 2019 02:43:07 +0200 Subject: [PATCH 2/6] make difference between pre-reservation and post-reservation unscheduled use --- constants/constants.js | 7 +- services/integration/doorLockCharges.js | 7 +- services/integration/reports.js | 85 ++++++++++++++++++++----- 3 files changed, 76 insertions(+), 23 deletions(-) diff --git a/constants/constants.js b/constants/constants.js index 89ded66..8f6f966 100644 --- a/constants/constants.js +++ b/constants/constants.js @@ -62,9 +62,10 @@ const integrationServiceErrors = { const incidentType = { NOT_AN_INCIDENT: 1, UNLOCKED_INCIDENT_RELATED_WITH_RESERVATION: 2, - UNSCHEDULED_INCIDENT_RELATED_WITH_RESERVATION: 3, - UNLOCKED_INCIDENT_STANDALONE: 4, - UNSCHEDULED_INCIDENT_STANDALONE: 5, + UNSCHEDULED_INCIDENT_BEFORE_RESERVATION: 3, + UNSCHEDULED_INCIDENT_AFTER_RESERVATION: 4, + UNLOCKED_INCIDENT_STANDALONE: 5, + UNSCHEDULED_INCIDENT_STANDALONE: 6, }; const UI_TIMEZONE = process.env.UI_TIMEZONE || 'America/Los_Angeles'; diff --git a/services/integration/doorLockCharges.js b/services/integration/doorLockCharges.js index c1f9599..59b1e81 100644 --- a/services/integration/doorLockCharges.js +++ b/services/integration/doorLockCharges.js @@ -400,7 +400,7 @@ const getIncidentData = (reservation) => { // 1. Check if member entered before reservation start time if (unlockEntry && chargeBefore && !previousReservationIsBackToBack) { incidents.push({ - incidentType: incidentType.UNSCHEDULED_INCIDENT_RELATED_WITH_RESERVATION, + incidentType: incidentType.UNSCHEDULED_INCIDENT_BEFORE_RESERVATION, reservation, unlockTimestamp: unlockEntry.timestamp, lockTimestamp: null, @@ -415,7 +415,7 @@ const getIncidentData = (reservation) => { // 2. Check if member left after reservation end time if (lockEntry && chargeAfter && !nextReservationIsBackToBack) { incidents.push({ - incidentType: incidentType.UNSCHEDULED_INCIDENT_RELATED_WITH_RESERVATION, + incidentType: incidentType.UNSCHEDULED_INCIDENT_AFTER_RESERVATION, reservation, unlockTimestamp: null, lockTimestamp: lockEntry.timestamp, @@ -508,7 +508,8 @@ const calculateDoorLockCharges = () => { case incidentType.UNLOCKED_INCIDENT_STANDALONE: unlockedIncidents.push(incident); break; - case incidentType.UNSCHEDULED_INCIDENT_RELATED_WITH_RESERVATION: + case incidentType.UNSCHEDULED_INCIDENT_BEFORE_RESERVATION: + case incidentType.UNSCHEDULED_INCIDENT_AFTER_RESERVATION: case incidentType.UNSCHEDULED_INCIDENT_STANDALONE: unscheduledIncidents.push(incident); break; diff --git a/services/integration/reports.js b/services/integration/reports.js index 8cb5e87..ed05c6f 100644 --- a/services/integration/reports.js +++ b/services/integration/reports.js @@ -11,17 +11,34 @@ const { fetchAllMembers } = require('../officeRnD/members'); const { fetchOffices, fetchResources } = require('../officeRnD/resources'); const getUnlockedIncidents = (startDate, endDate, memberId) => { - const attributes = ['id', 'reservationId', 'memberId', 'resourceId', 'bookingStart', 'bookingEnd', 'incidentLevel', 'incidentLevelPrice']; + const attributes = ['id', 'reservationId', 'memberId', 'resourceId', 'bookingStart', 'bookingEnd', 'unlockTimestamp', 'incidentLevel', 'incidentLevelPrice']; const filters = {}; if (startDate && endDate) { - filters.bookingStart = { - [Op.and]: { - [Op.gte]: startDate.utc().toISOString(), - [Op.lte]: endDate.utc().toISOString(), + const bookingStartCondition = { + bookingStart: { + [Op.and]: { + [Op.gte]: startDate.toISOString(), + [Op.lte]: endDate.toISOString(), + } } - } + }; + + const unlockTimestampCondition = { + unlockTimestamp: { + [Op.and]: { + [Op.gte]: startDate.toISOString(), + [Op.lte]: endDate.toISOString(), + } + } + }; + + const bookingStartOrUnlockTimestamp = { + [Op.or]: [bookingStartCondition, unlockTimestampCondition] + }; + + Object.assign(filters, bookingStartOrUnlockTimestamp); } if (memberId){ @@ -45,8 +62,8 @@ const getUnscheduledIncidents = (startDate, endDate, memberId) => { 'resourceId', 'bookingStart', 'bookingEnd', - 'doorLockEventTimestamp', - 'doorLockEventType', + 'unlockTimestamp', + 'lockTimestamp', 'timeIntervalsToCharge', 'chargePrice', 'totalChargeFee' @@ -55,14 +72,32 @@ const getUnscheduledIncidents = (startDate, endDate, memberId) => { const filters = {}; if (startDate && endDate) { - filters.bookingStart = { - [Op.and]: { - [Op.gte]: startDate.utc().toISOString(), - [Op.lte]: endDate.utc().toISOString(), + const bookingStartCondition = { + bookingStart: { + [Op.and]: { + [Op.gte]: startDate.toISOString(), + [Op.lte]: endDate.toISOString(), + } } - } + }; + + const unlockTimestampCondition = { + unlockTimestamp: { + [Op.and]: { + [Op.gte]: startDate.toISOString(), + [Op.lte]: endDate.toISOString(), + } + } + }; + + const bookingStartOrUnlockTimestamp = { + [Op.or]: [bookingStartCondition, unlockTimestampCondition] + }; + + Object.assign(filters, bookingStartOrUnlockTimestamp); } + if (memberId){ filters.memberId = memberId; } @@ -77,7 +112,12 @@ const getUnscheduledIncidents = (startDate, endDate, memberId) => { }; const formatTime = (timestamp) => { - return moment.tz(timestamp, UI_TIMEZONE).format('MM/DD/YYYY hh:mm a'); + const momentObject = moment.tz(timestamp, UI_TIMEZONE); + if (momentObject.isValid()){ + return momentObject.format('MM/DD/YYYY hh:mm a'); + }else{ + return null; + } }; const getAllDoorLockIncidents = (dateRange, memberId) => { @@ -115,7 +155,7 @@ const getAllDoorLockIncidents = (dateRange, memberId) => { const allIncidents = []; unlockedIncidents.forEach((unlockedIncident) => { - const incidentTypeNumber = unlockedIncident.reservationId.length > 0 ? + const incidentTypeNumber = unlockedIncident.reservationId ? incidentType.UNLOCKED_INCIDENT_RELATED_WITH_RESERVATION : incidentType.UNLOCKED_INCIDENT_STANDALONE; allIncidents.push({ incidentId: unlockedIncident.id, @@ -125,6 +165,7 @@ const getAllDoorLockIncidents = (dateRange, memberId) => { officeName: officesMap[resourcesMap[unlockedIncident.resourceId].officeId].officeName, bookingStart: formatTime(unlockedIncident.bookingStart), bookingEnd: formatTime(unlockedIncident.bookingEnd), + unlockTimestamp: formatTime(unlockedIncident.unlockTimestamp), incidentType: incidentTypeNumber, incidentLevel: unlockedIncident.incidentLevel, incidentPrice: unlockedIncident.incidentLevelPrice, @@ -132,8 +173,16 @@ const getAllDoorLockIncidents = (dateRange, memberId) => { }); unscheduledIncidents.forEach((unscheduledIncident) => { - const incidentTypeNumber = unscheduledIncident.reservationId.length > 0 ? - incidentType.UNSCHEDULED_INCIDENT_RELATED_WITH_RESERVATION : incidentType.UNSCHEDULED_INCIDENT_STANDALONE; + let incidentTypeNumber; + if (unscheduledIncident.reservationId){ + if (unscheduledIncident.unlockTimestamp && !unscheduledIncident.lockTimestamp){ + incidentTypeNumber = incidentType.UNSCHEDULED_INCIDENT_BEFORE_RESERVATION; + }else{ + incidentTypeNumber = incidentType.UNSCHEDULED_INCIDENT_AFTER_RESERVATION; + } + }else{ + incidentTypeNumber = incidentType.UNSCHEDULED_INCIDENT_STANDALONE; + } allIncidents.push({ incidentId: unscheduledIncident.id, memberId: unscheduledIncident.memberId, @@ -142,6 +191,8 @@ const getAllDoorLockIncidents = (dateRange, memberId) => { officeName: officesMap[resourcesMap[unscheduledIncident.resourceId].officeId].officeName, bookingStart: formatTime(unscheduledIncident.bookingStart), bookingEnd: formatTime(unscheduledIncident.bookingEnd), + unlockTimestamp: formatTime(unscheduledIncident.unlockTimestamp), + lockTimestamp: formatTime(unscheduledIncident.lockTimestamp), incidentType: incidentTypeNumber, timeIntervalsToCharge: unscheduledIncident.timeIntervalsToCharge, chargePrice: unscheduledIncident.chargePrice, From e3eee4139b5c33174cf9b762d0930768873d39b6 Mon Sep 17 00:00:00 2001 From: Bilal Catic Date: Sun, 7 Jul 2019 02:44:34 +0200 Subject: [PATCH 3/6] modify UI to display incidents separately --- .../components/MemberIncidentsTable/index.js | 29 ++++---- client/src/constants/enums.js | 15 ++-- client/src/constants/menuItems.js | 2 + client/src/scenes/IncidentsReport/index.js | 72 ++++++++++++++++++- .../components/MemberSummary.js | 6 +- 5 files changed, 102 insertions(+), 22 deletions(-) diff --git a/client/src/components/MemberIncidentsTable/index.js b/client/src/components/MemberIncidentsTable/index.js index 6f79f59..01dac02 100644 --- a/client/src/components/MemberIncidentsTable/index.js +++ b/client/src/components/MemberIncidentsTable/index.js @@ -8,13 +8,13 @@ import {incidentsReportHeaderTitles} from '../../constants/menuItems'; import { incidentDescriptions, incidentLevelDescriptions, - UNLOCKED_INCIDENT, - UNSCHEDULED_INCIDENT + UNLOCKED_INCIDENT_RELATED_WITH_RESERVATION, UNLOCKED_INCIDENT_STANDALONE, UNSCHEDULED_INCIDENT_AFTER_RESERVATION, + UNSCHEDULED_INCIDENT_BEFORE_RESERVATION, UNSCHEDULED_INCIDENT_STANDALONE } from '../../constants/enums'; const MemberIncidentsTable = props => { - const { loading, title, openMemberSummaryOnMemberClick } = props; + const { loading, title, openMemberSummaryOnMemberClick, showBookingTimes, showDoorLockEntryTimes } = props; const incidents = props.incidents ? props.incidents : []; const columns = []; @@ -24,7 +24,15 @@ const MemberIncidentsTable = props => { incidentHeaders.forEach((header) => { const columnTitle = incidentsReportHeaderTitles[header]; - if (columnTitle){ + let showColumn = true; + if ((header === 'bookingStart' || header === 'bookingEnd') && !showBookingTimes){ + showColumn = false; + } + if ((header === 'unlockTimestamp' || header === 'lockTimestamp') && !showDoorLockEntryTimes){ + showColumn = false; + } + + if (columnTitle && showColumn){ const columnAlignments = { left: 'left', right: 'right', @@ -54,10 +62,13 @@ const MemberIncidentsTable = props => { const { incidentType, incidentLevel, timeIntervalsToCharge } = props.row['_original']; switch (incidentType) { - case UNLOCKED_INCIDENT: + case UNLOCKED_INCIDENT_RELATED_WITH_RESERVATION: + case UNLOCKED_INCIDENT_STANDALONE: cellValue = `${incidentLevelDescriptions[incidentLevel]}`; break; - case UNSCHEDULED_INCIDENT: + case UNSCHEDULED_INCIDENT_BEFORE_RESERVATION: + case UNSCHEDULED_INCIDENT_AFTER_RESERVATION: + case UNSCHEDULED_INCIDENT_STANDALONE: cellValue = `${timeIntervalsToCharge} x 5 min`; break; default: @@ -80,12 +91,6 @@ const MemberIncidentsTable = props => { }else{ return
{cellValue}
} - - // return - //
{cellValue}
- //
- - // return
{cellValue}
} }); } diff --git a/client/src/constants/enums.js b/client/src/constants/enums.js index a6da2d0..2a3f642 100644 --- a/client/src/constants/enums.js +++ b/client/src/constants/enums.js @@ -1,10 +1,16 @@ -export const UNLOCKED_INCIDENT = 2; -export const UNSCHEDULED_INCIDENT = 3; +export const UNLOCKED_INCIDENT_RELATED_WITH_RESERVATION = 2; +export const UNSCHEDULED_INCIDENT_BEFORE_RESERVATION = 3; +export const UNSCHEDULED_INCIDENT_AFTER_RESERVATION = 4; +export const UNLOCKED_INCIDENT_STANDALONE = 5; +export const UNSCHEDULED_INCIDENT_STANDALONE = 6; export const incidentDescriptions = {}; -incidentDescriptions[UNLOCKED_INCIDENT] = 'User left door unlocked'; -incidentDescriptions[UNSCHEDULED_INCIDENT] = 'Unscheduled use'; +incidentDescriptions[UNLOCKED_INCIDENT_RELATED_WITH_RESERVATION] = 'User left door unlocked'; +incidentDescriptions[UNSCHEDULED_INCIDENT_BEFORE_RESERVATION] = 'Unscheduled use - before'; +incidentDescriptions[UNSCHEDULED_INCIDENT_AFTER_RESERVATION] = 'Unscheduled use - after'; +incidentDescriptions[UNLOCKED_INCIDENT_STANDALONE] = 'User left door unlocked'; +incidentDescriptions[UNSCHEDULED_INCIDENT_STANDALONE] = 'Unscheduled use'; export const incidentLevelDescriptions = { UNLOCKED_0: 'First month', @@ -13,5 +19,4 @@ export const incidentLevelDescriptions = { UNLOCKED_3: 'Fourth month', UNLOCKED_4: 'Fifth month', UNLOCKED_5: 'Sixth month', - }; diff --git a/client/src/constants/menuItems.js b/client/src/constants/menuItems.js index 07b6c96..8a83cfc 100644 --- a/client/src/constants/menuItems.js +++ b/client/src/constants/menuItems.js @@ -45,6 +45,8 @@ export const incidentsReportHeaderTitles = { resourceName: 'Room', bookingStart: 'Reservation Start', bookingEnd: 'Reservation End', + unlockTimestamp: 'Unlock Time', + lockTimestamp: 'Lock Time', memberName: 'Member Name', incidentType: 'Incident Type', feeDescription: 'Fee description', diff --git a/client/src/scenes/IncidentsReport/index.js b/client/src/scenes/IncidentsReport/index.js index fa464ac..8194ee7 100644 --- a/client/src/scenes/IncidentsReport/index.js +++ b/client/src/scenes/IncidentsReport/index.js @@ -1,6 +1,6 @@ import React, { Component } from 'react'; import { connect } from 'react-redux'; -import { Container } from 'semantic-ui-react'; +import { Container, Accordion, Label } from 'semantic-ui-react'; import MainMenu from '../../components/MainMenu'; import DateRangePicker from '../../components/DateRangePicker'; @@ -8,6 +8,13 @@ import MemberIncidentsTable from '../../components/MemberIncidentsTable'; import { fetchIncidents } from '../../store/actions'; +import { + UNLOCKED_INCIDENT_RELATED_WITH_RESERVATION, + UNLOCKED_INCIDENT_STANDALONE, UNSCHEDULED_INCIDENT_AFTER_RESERVATION, + UNSCHEDULED_INCIDENT_BEFORE_RESERVATION, + UNSCHEDULED_INCIDENT_STANDALONE +} from '../../constants/enums'; + class IncidentsReport extends Component { onDatesUpdate(dateRange) { const { fetchIncidents } = this.props; @@ -17,6 +24,62 @@ class IncidentsReport extends Component { render () { const { pendingIncidents, incidents } = this.props; + const incidentsRelatedToReservations = []; + const standaloneIncidents = []; + + if (Array.isArray(incidents)){ + incidents.forEach((incident) => { + if (incident && incident.incidentType){ + switch (incident.incidentType) { + case UNLOCKED_INCIDENT_RELATED_WITH_RESERVATION: + case UNSCHEDULED_INCIDENT_BEFORE_RESERVATION: + case UNSCHEDULED_INCIDENT_AFTER_RESERVATION: + incidentsRelatedToReservations.push(incident); + break; + case UNLOCKED_INCIDENT_STANDALONE: + case UNSCHEDULED_INCIDENT_STANDALONE: + standaloneIncidents.push(incident); + break; + } + } + }); + } + + const incidentsRelatedToReservationsTable = ( + + ); + + const standaloneIncidentsTable = ( + + ); + + const accordionPanels = [ + { + key: 'related-door-lock-incidents', + title : { content: