From 7aab1e538e6c36cdcb8d27821212f558f94409ce Mon Sep 17 00:00:00 2001 From: Bilal Catic Date: Thu, 27 Jun 2019 05:38:45 +0200 Subject: [PATCH 1/4] fetch reservations, find and update changed reservations --- cronServices/checkBookingChanges.js | 24 +++++++ ...servations-table-remove-old-primary-key.js | 14 ++++ ...-reservations-table-add-new-primary-key.js | 14 ++++ models/bookingReservation.js | 6 +- package.json | 3 +- services/integration/bookingChangeCharges.js | 2 + services/officeRnD/bookings.js | 68 ++++++++++++++++++- 7 files changed, 127 insertions(+), 4 deletions(-) create mode 100644 cronServices/checkBookingChanges.js create mode 100644 migrations/20190626135045-alter-booking-reservations-table-remove-old-primary-key.js create mode 100644 migrations/20190626173324-alter-booking-reservations-table-add-new-primary-key.js create mode 100644 services/integration/bookingChangeCharges.js diff --git a/cronServices/checkBookingChanges.js b/cronServices/checkBookingChanges.js new file mode 100644 index 0000000..a3befcc --- /dev/null +++ b/cronServices/checkBookingChanges.js @@ -0,0 +1,24 @@ +'use strict'; + +require('dotenv').config(); + +const { fetchAllBookings, bulkWriteReservationsWithChangesTracking } = require('../services/officeRnD/bookings'); + +const checkBookingChanges = () => { + fetchAllBookings() + .then((reservations) => { + bulkWriteReservationsWithChangesTracking(reservations) + .then((changes) => { + console.log('== CHANGES == '); + console.log(changes); + }) + .catch((error) => { + console.log('Error bulk write booking reservations :', error); + }); + }) + .catch((error) => { + console.log('Error fetching bookings from ORD ', error); + }); +}; + +checkBookingChanges(); diff --git a/migrations/20190626135045-alter-booking-reservations-table-remove-old-primary-key.js b/migrations/20190626135045-alter-booking-reservations-table-remove-old-primary-key.js new file mode 100644 index 0000000..bdf5ea5 --- /dev/null +++ b/migrations/20190626135045-alter-booking-reservations-table-remove-old-primary-key.js @@ -0,0 +1,14 @@ +'use strict'; + +module.exports = { + up: (queryInterface, Sequelize) => { + return queryInterface.removeConstraint('bookingReservations', 'bookingReservations_pkey'); + }, + + down: (queryInterface, Sequelize) => { + return queryInterface.addConstraint('bookingReservations', ['id'], { + name: 'bookingReservations_pkey', + type: 'PRIMARY KEY', + }); + } +}; diff --git a/migrations/20190626173324-alter-booking-reservations-table-add-new-primary-key.js b/migrations/20190626173324-alter-booking-reservations-table-add-new-primary-key.js new file mode 100644 index 0000000..9cb25fb --- /dev/null +++ b/migrations/20190626173324-alter-booking-reservations-table-add-new-primary-key.js @@ -0,0 +1,14 @@ +'use strict'; + +module.exports = { + up: (queryInterface, Sequelize) => { + return queryInterface.addConstraint('bookingReservations', ['reservationId'], { + name: 'bookingReservations_pkey', + type: 'PRIMARY KEY', + }); + }, + + down: (queryInterface, Sequelize) => { + return queryInterface.removeConstraint('bookingReservations', 'bookingReservations_pkey'); + } +}; diff --git a/models/bookingReservation.js b/models/bookingReservation.js index d977de3..9d13ba5 100644 --- a/models/bookingReservation.js +++ b/models/bookingReservation.js @@ -2,7 +2,10 @@ module.exports = (sequelize, DataTypes) => { const bookingReservation = sequelize.define('bookingReservation', { - reservationId: DataTypes.TEXT, + reservationId: { + type: DataTypes.TEXT, + primaryKey: true, + }, memberId: DataTypes.TEXT, officeId: DataTypes.TEXT, resourceId: DataTypes.TEXT, @@ -10,7 +13,6 @@ module.exports = (sequelize, DataTypes) => { end: DataTypes.DATE, timezone: DataTypes.TEXT, canceled: DataTypes.BOOLEAN, - }, {}); bookingReservation.associate = function(models) { // associations can be defined here diff --git a/package.json b/package.json index c51ec9a..952c593 100644 --- a/package.json +++ b/package.json @@ -15,7 +15,8 @@ "start-server": "nodemon server.js", "start-client": "cd client && yarn start", "start": "node server.js", - "heroku-postbuild": "NPM_CONFIG_PRODUCTION=false npm install --prefix client && npm run build --prefix client" + "heroku-postbuild": "NPM_CONFIG_PRODUCTION=false npm install --prefix client && npm run build --prefix client", + "check-booking-changes": "node ./cronServices/checkBookingChanges.js" }, "engines": { "node": "11.12.x" diff --git a/services/integration/bookingChangeCharges.js b/services/integration/bookingChangeCharges.js new file mode 100644 index 0000000..eb109ab --- /dev/null +++ b/services/integration/bookingChangeCharges.js @@ -0,0 +1,2 @@ +'use strict'; + diff --git a/services/officeRnD/bookings.js b/services/officeRnD/bookings.js index c31da33..11fe373 100644 --- a/services/officeRnD/bookings.js +++ b/services/officeRnD/bookings.js @@ -148,11 +148,77 @@ const writeBookingReservation = (bookingReservation) => { return db.bookingReservation.findOrCreate({where: {...bookingReservation}, defaults: {...bookingReservation}}); }; +const bulkWriteReservationsWithChangesTracking = (reservations) => { + return new Promise ((resolve, reject) => { + const changes = []; + const asyncJobs = []; + + db.bookingReservation.addHook('beforeUpdate', 'updateHook', (instance) => { + const changedKeys = instance.changed(); + const previous = instance.previous(); + + const indexOfUpdatedAt = changedKeys.indexOf('updatedAt'); + + if (indexOfUpdatedAt !== -1){ + changedKeys.splice(indexOfUpdatedAt, 1); + } + + if (changedKeys.length > 0){ + //check if there is really difference, reservation start and end timestamps are reported as changed but they are not + + let realChange = false; + changedKeys.forEach((changedKey) => { + if (JSON.stringify(previous[changedKey]) !== JSON.stringify(instance[changedKey])){ + realChange = true; + } + }); + + if (realChange){ + changes.push({ + oldReservation: previous, + newReservation: instance.get(), + }); + } + } + }); + + reservations.forEach((reservation) => { + asyncJobs.push( + db.bookingReservation.update(reservation, { + where: { + reservationId: reservation.reservationId, + }, + returning: true, + individualHooks: true, + }) + .then(([updateCount, updatedInstances]) => { + if (updateCount === 0){ + db.bookingReservation.upsert(reservation); + } + }) + .catch((error) => { + console.log('Error updating'); + console.log(error); + reject(error); + }) + ); + }); + + Promise.all(asyncJobs) + .then(() => { + db.bookingReservation.removeHook('updateHook'); + resolve(changes); + }) + .catch((error) => reject(error)); + }); +}; + module.exports = { fetchAllBookings, writeBookingReservation, getAllFinishedBookings, getFirstNextBooking, getFirstPreviousBooking, - getFirstReservationInBlock + getFirstReservationInBlock, + bulkWriteReservationsWithChangesTracking, }; -- 2.47.3 From 6bed9aecc009fec983c1f3d76e1518c54c31a9f0 Mon Sep 17 00:00:00 2001 From: Bilal Catic Date: Mon, 8 Jul 2019 18:23:32 +0200 Subject: [PATCH 2/4] create incidents for booking changes --- constants/constants.js | 5 + cronServices/checkBookingChanges.js | 15 ++- environment.env | 2 + ...9-create-booking-change-incidents-table.js | 34 ++++++ ...olumn-to-the-booking-reservations-table.js | 14 +++ models/bookingChangeIncident.js | 19 +++ models/bookingReservation.js | 1 + services/integration/bookingChangeCharges.js | 113 ++++++++++++++++++ services/officeRnD/bookings.js | 50 ++++---- 9 files changed, 230 insertions(+), 23 deletions(-) create mode 100644 migrations/20190708114639-create-booking-change-incidents-table.js create mode 100644 migrations/20190708120732-add-reservation-hourly-price-column-to-the-booking-reservations-table.js create mode 100644 models/bookingChangeIncident.js diff --git a/constants/constants.js b/constants/constants.js index 8f6f966..9ccad10 100644 --- a/constants/constants.js +++ b/constants/constants.js @@ -66,6 +66,8 @@ const incidentType = { UNSCHEDULED_INCIDENT_AFTER_RESERVATION: 4, UNLOCKED_INCIDENT_STANDALONE: 5, UNSCHEDULED_INCIDENT_STANDALONE: 6, + BOOKING_MOVED_TO_ANOTHER_DAY: 7, + BOOKING_SHORTENED: 8, }; const UI_TIMEZONE = process.env.UI_TIMEZONE || 'America/Los_Angeles'; @@ -76,6 +78,8 @@ const MAX_BACK_TO_BACK_DIFFERENCE = parseInt(process.env.MAX_BACK_TO_BACK_DIFFER const UNSCHEDULED_TIME_RESOLUTION = parseInt(process.env.UNSCHEDULED_USE_TIME_RESOLUTION) || 5; const UNSCHEDULED_CHARGE_PRICE = parseFloat(process.env.UNSCHEDULED_USE_CHARGE_PRICE) || 5; +const BOOKING_CHANGE_PERCENTAGE_CHARGE = parseInt(process.env.BOOKING_CHANGE_PERCENTAGE_CHARGE) || 100; + module.exports = { VALID_CSV_HEADERS, USER_ENTRY_EVENT, @@ -92,4 +96,5 @@ module.exports = { MAX_BACK_TO_BACK_DIFFERENCE, UNSCHEDULED_TIME_RESOLUTION, UNSCHEDULED_CHARGE_PRICE, + BOOKING_CHANGE_PERCENTAGE_CHARGE, }; diff --git a/cronServices/checkBookingChanges.js b/cronServices/checkBookingChanges.js index a3befcc..00451a3 100644 --- a/cronServices/checkBookingChanges.js +++ b/cronServices/checkBookingChanges.js @@ -1,23 +1,32 @@ 'use strict'; - require('dotenv').config(); const { fetchAllBookings, bulkWriteReservationsWithChangesTracking } = require('../services/officeRnD/bookings'); +const { chargeBookingChanges } = require('../services/integration/bookingChangeCharges'); + const checkBookingChanges = () => { fetchAllBookings() .then((reservations) => { bulkWriteReservationsWithChangesTracking(reservations) .then((changes) => { - console.log('== CHANGES == '); - console.log(changes); + chargeBookingChanges(changes) + .then(() => { + process.exit(); + }) + .catch((error) => { + console.log('Error creating charges ', error); + process.exit(); + }); }) .catch((error) => { console.log('Error bulk write booking reservations :', error); + process.exit(); }); }) .catch((error) => { console.log('Error fetching bookings from ORD ', error); + process.exit(); }); }; diff --git a/environment.env b/environment.env index f9172c0..1ea3b01 100644 --- a/environment.env +++ b/environment.env @@ -18,3 +18,5 @@ UNLOCK_4=Price for unlocked door, fifth month UNLOCK_5=Price for unlocked door, sixth month UNLOCK_STREAK_REPAIR_AFTER=Number of months without incidents to reset user incident level + +BOOKING_CHANGE_PERCENTAGE_CHARGE=Percentage of hourly reate to apply for cancellation-like charges diff --git a/migrations/20190708114639-create-booking-change-incidents-table.js b/migrations/20190708114639-create-booking-change-incidents-table.js new file mode 100644 index 0000000..8c6e382 --- /dev/null +++ b/migrations/20190708114639-create-booking-change-incidents-table.js @@ -0,0 +1,34 @@ +'use strict'; + +module.exports = { + up: (queryInterface, Sequelize) => { + return queryInterface.createTable('bookingChangeIncidents', { + id: { + allowNull: false, + autoIncrement: true, + primaryKey: true, + type: Sequelize.INTEGER + }, + reservationId: Sequelize.TEXT, + memberId: Sequelize.TEXT, + resourceId: Sequelize.TEXT, + oldBookingStart: Sequelize.DATE, + oldBookingEnd: Sequelize.DATE, + newBookingStart: Sequelize.DATE, + newBookingEnd: Sequelize.DATE, + incidentType: Sequelize.INTEGER, + chargeFee: Sequelize.FLOAT, + createdAt: { + allowNull: false, + type: Sequelize.DATE + }, + updatedAt: { + allowNull: false, + type: Sequelize.DATE + } + }); + }, + down: (queryInterface, Sequelize) => { + return queryInterface.dropTable('bookingChangeIncidents'); + } +}; diff --git a/migrations/20190708120732-add-reservation-hourly-price-column-to-the-booking-reservations-table.js b/migrations/20190708120732-add-reservation-hourly-price-column-to-the-booking-reservations-table.js new file mode 100644 index 0000000..1cd1aca --- /dev/null +++ b/migrations/20190708120732-add-reservation-hourly-price-column-to-the-booking-reservations-table.js @@ -0,0 +1,14 @@ +'use strict'; + +module.exports = { + up: (queryInterface, Sequelize) => { + return queryInterface.addColumn('bookingReservations', 'hourlyRate', { + type: Sequelize.FLOAT, + after: 'end', + }); + }, + + down: (queryInterface, Sequelize) => { + return queryInterface.removeColumn('bookingReservations', 'hourlyRate'); + } +}; diff --git a/models/bookingChangeIncident.js b/models/bookingChangeIncident.js new file mode 100644 index 0000000..1a91f1e --- /dev/null +++ b/models/bookingChangeIncident.js @@ -0,0 +1,19 @@ +'use strict'; + +module.exports = (sequelize, DataTypes) => { + const bookingChangeIncident = sequelize.define('bookingChangeIncident', { + reservationId: DataTypes.TEXT, + memberId: DataTypes.TEXT, + resourceId: DataTypes.TEXT, + oldBookingStart: DataTypes.DATE, + oldBookingEnd: DataTypes.DATE, + newBookingStart: DataTypes.DATE, + newBookingEnd: DataTypes.DATE, + incidentType: DataTypes.INTEGER, + chargeFee: DataTypes.FLOAT, + }, {}); + bookingChangeIncident.associate = function(models) { + // associations can be defined here + }; + return bookingChangeIncident; +}; diff --git a/models/bookingReservation.js b/models/bookingReservation.js index 9d13ba5..d6ba4c7 100644 --- a/models/bookingReservation.js +++ b/models/bookingReservation.js @@ -11,6 +11,7 @@ module.exports = (sequelize, DataTypes) => { resourceId: DataTypes.TEXT, start: DataTypes.DATE, end: DataTypes.DATE, + hourlyRate: DataTypes.FLOAT, timezone: DataTypes.TEXT, canceled: DataTypes.BOOLEAN, }, {}); diff --git a/services/integration/bookingChangeCharges.js b/services/integration/bookingChangeCharges.js index eb109ab..13d47b2 100644 --- a/services/integration/bookingChangeCharges.js +++ b/services/integration/bookingChangeCharges.js @@ -1,2 +1,115 @@ 'use strict'; +const moment = require('moment-timezone'); +const db = require('../../models/index'); + +const { UI_TIMEZONE, BOOKING_CHANGE_PERCENTAGE_CHARGE, incidentType } = require('../../constants/constants'); + +const bulkWriteBookingChangeIncidents = (incidents) => { + return new Promise((resolve, reject) => { + const asyncJobs = []; + incidents.forEach((incident) => { + asyncJobs.push(db.bookingChangeIncident.findOrCreate({where: incident, defaults: incident})); + }); + + Promise.all(asyncJobs) + .then(() => { + resolve(); + }) + .catch((error) => reject(error)); + }); +}; + +const chargeBookingChanges = (changes) => { + return new Promise((resolve, reject) => { + if (Array.isArray(changes)){ + const incidents = []; + const errors = []; + changes.forEach((change) => { + const { oldReservation, newReservation } = change; + if (oldReservation && newReservation){ + const oldStart = oldReservation.start ? moment.utc(oldReservation.start) : null; + const oldEnd = oldReservation.end ? moment.utc(oldReservation.end) : null; + + const newStart = newReservation.start ? moment.utc(newReservation.start) : null; + const newEnd = newReservation.end ? moment.utc(newReservation.end) : null; + + const reservationTimezone = newReservation.timezone ? newReservation.timezone : UI_TIMEZONE; + const reservationHourlyRate = newReservation.hourlyRate ? newReservation.hourlyRate : undefined; + + if (oldStart && oldEnd && newStart && newEnd && reservationHourlyRate){ + const oldReservationLength = oldEnd.diff(oldStart, 'hours', true); + const newReservationLength = newEnd.diff(newStart, 'hours', true); + + const differenceFromNow = oldStart.diff(moment.utc(), 'hours'); + + if (differenceFromNow && (differenceFromNow < 24)){ + // Changed reservation that was within 24hrs from now + + // Check if new reservation is on same day + const sameDay = oldStart.tz(reservationTimezone).isSame(newStart.tz(reservationTimezone), 'day'); + + const { reservationId, memberId, resourceId, hourlyRate } = newReservation; + + if (sameDay){ + // Reservation moved in same day + // Check if member shortened the reservation + + if (newReservationLength < oldReservationLength){ + + const differenceInLength = oldReservationLength - newReservationLength; + const chargeFee = differenceInLength*hourlyRate*BOOKING_CHANGE_PERCENTAGE_CHARGE/100; + + const incident = { + reservationId, + memberId, + resourceId, + oldBookingStart: oldReservation.start, + oldBookingEnd: oldReservation.end, + newBookingStart: newReservation.start, + newBookingEnd: newReservation.end, + incidentType: incidentType.BOOKING_SHORTENED, + chargeFee, + }; + + incidents.push(incident); + } + }else{ + // Reservation moved to another day + // Add cancellation charge + const chargeFee = oldReservationLength*hourlyRate*BOOKING_CHANGE_PERCENTAGE_CHARGE/100; + + const incident = { + reservationId, + memberId, + resourceId, + oldBookingStart: oldReservation.start, + oldBookingEnd: oldReservation.end, + newBookingStart: newReservation.start, + newBookingEnd: newReservation.end, + incidentType: incidentType.BOOKING_MOVED_TO_ANOTHER_DAY, + chargeFee, + }; + + incidents.push(incident); + } + } + }else{ + errors.push(change); + } + } + }); + if (errors.length > 0){ + console.log('There were some errors with incomplete bookings : '); + console.log(errors); + } + resolve(bulkWriteBookingChangeIncidents(incidents)); + }else{ + reject('Input argument is not an array !'); + } + }); +}; + +module.exports = { + chargeBookingChanges, +}; diff --git a/services/officeRnD/bookings.js b/services/officeRnD/bookings.js index 11fe373..3a63a6c 100644 --- a/services/officeRnD/bookings.js +++ b/services/officeRnD/bookings.js @@ -15,6 +15,10 @@ const fetchAllBookings = () => { const bookingData = result && result.data ? result.data : []; bookingData.forEach((fullBookingEntry) => { + const fees = fullBookingEntry && fullBookingEntry.fees ? fullBookingEntry.fees : []; + const firstFee = fees.length > 0 && fees[0].fee ? fees[0].fee : undefined; + const hourlyRate = firstFee && firstFee.price ? firstFee.price : undefined; + cleanedBookingReservations.push({ reservationId: fullBookingEntry['_id'], memberId: fullBookingEntry.member, @@ -24,6 +28,7 @@ const fetchAllBookings = () => { end: fullBookingEntry.end.dateTime, timezone: fullBookingEntry.timezone, canceled: fullBookingEntry.canceled || false, + hourlyRate, }); }); resolve(cleanedBookingReservations); @@ -145,7 +150,19 @@ const getFirstReservationInBlock = (reservation) => { }; const writeBookingReservation = (bookingReservation) => { - return db.bookingReservation.findOrCreate({where: {...bookingReservation}, defaults: {...bookingReservation}}); + const { reservationId, memberId, officeId, resourceId, start, end, timezone, canceled, hourlyRate } = bookingReservation; + const bookingReservationForDB = { + reservationId, + memberId, + officeId, + resourceId, + start, + end, + timezone, + canceled, + hourlyRate, + }; + return db.bookingReservation.findOrCreate({where: {...bookingReservationForDB}, defaults: {...bookingReservationForDB}}); }; const bulkWriteReservationsWithChangesTracking = (reservations) => { @@ -157,28 +174,21 @@ const bulkWriteReservationsWithChangesTracking = (reservations) => { const changedKeys = instance.changed(); const previous = instance.previous(); - const indexOfUpdatedAt = changedKeys.indexOf('updatedAt'); + const lookupKeys = ['start', 'end']; - if (indexOfUpdatedAt !== -1){ - changedKeys.splice(indexOfUpdatedAt, 1); - } - - if (changedKeys.length > 0){ - //check if there is really difference, reservation start and end timestamps are reported as changed but they are not - - let realChange = false; - changedKeys.forEach((changedKey) => { - if (JSON.stringify(previous[changedKey]) !== JSON.stringify(instance[changedKey])){ + let realChange = false; + lookupKeys.forEach((key) => { + if ((changedKeys.indexOf(key) !== -1) && + (JSON.stringify(previous[key]) !== JSON.stringify(instance[key]))){ realChange = true; - } - }); - - if (realChange){ - changes.push({ - oldReservation: previous, - newReservation: instance.get(), - }); } + }); + + if (realChange){ + changes.push({ + oldReservation: previous, + newReservation: instance.get(), + }); } }); -- 2.47.3 From c0d7418f3fdccff223a1bc976e7555fd588197cd Mon Sep 17 00:00:00 2001 From: Bilal Catic Date: Mon, 8 Jul 2019 18:50:10 +0200 Subject: [PATCH 3/4] include booking change incidents in report; refactor --- controllers/integration.js | 20 +++-------- routes/index.js | 6 ++-- services/integration/reports.js | 59 ++++++++++++++++++++++++++++++--- 3 files changed, 61 insertions(+), 24 deletions(-) diff --git a/controllers/integration.js b/controllers/integration.js index fd2cde0..235a774 100644 --- a/controllers/integration.js +++ b/controllers/integration.js @@ -1,7 +1,7 @@ 'use strict'; const { getMappingsFromDatabase, fetchOffices, fetchResources, saveNewMappingToDatabase } = require('../services/officeRnD/resources'); -const { getAllDoorLockIncidents } = require('../services/integration/reports'); +const { getAllIncidents } = require('../services/integration/reports'); const getKnownOfficeResourceMappings = (req, res) => { const dataToFetch = [getMappingsFromDatabase(), fetchOffices(), fetchResources() ]; @@ -32,13 +32,13 @@ const addNewMapping = (req, res) => { } }; -const getAllIncidents = (req, res) => { +const getAllIncidentsController = (req, res) => { const dateRange = { startDate: req.params.startDate, endDate: req.params.endDate, }; - getAllDoorLockIncidents(dateRange) + getAllIncidents(dateRange) .then((incidents) => { res.send(incidents); }) @@ -55,7 +55,7 @@ const getMemberIncidents = (req, res) => { endDate: req.params.endDate, }; - getAllDoorLockIncidents(dateRange, memberId) + getAllIncidents(dateRange, memberId) .then((incidents) => { res.send(incidents); }) @@ -65,19 +65,9 @@ const getMemberIncidents = (req, res) => { }); }; -const getUnlockedIncidents = (req, res) => { - -}; - -const getUnscheduledIncidents = (req, res) => { - -}; - module.exports = { getKnownOfficeResourceMappings, addNewMapping, - getAllIncidents, - getUnscheduledIncidents, - getUnlockedIncidents, + getAllIncidentsController, getMemberIncidents, }; diff --git a/routes/index.js b/routes/index.js index abe717c..0a6f01a 100644 --- a/routes/index.js +++ b/routes/index.js @@ -2,7 +2,7 @@ const { apiStatusCheck } = require('../controllers/apiStatusCheck'); const { uploadDoorLockData } = require('../controllers/doorLock'); -const { getKnownOfficeResourceMappings, addNewMapping, getAllIncidents, getMemberIncidents,getUnscheduledIncidents, getUnlockedIncidents } = require('../controllers/integration'); +const { getKnownOfficeResourceMappings, addNewMapping, getAllIncidentsController, getMemberIncidents } = require('../controllers/integration'); const { fetchMembersList } = require('../controllers/officeRnD'); const { calculateDoorLockCharges } = require('../services/integration/doorLockCharges'); @@ -17,9 +17,7 @@ router.get('/integration/mappings', getKnownOfficeResourceMappings); router.post('/integration/mappings', addNewMapping); router.get('/integration/report/member/:memberId/:startDate/:endDate', getMemberIncidents); -router.get('/integration/report/allIncidents/:startDate/:endDate', getAllIncidents); -router.get('/integration/report/unlockedIncidents', getUnlockedIncidents); -router.get('/integration/report/unscheduledIncidents', getUnscheduledIncidents); +router.get('/integration/report/allIncidents/:startDate/:endDate', getAllIncidentsController); router.get('/officeRnD/membersList', fetchMembersList); diff --git a/services/integration/reports.js b/services/integration/reports.js index ed05c6f..8a76cac 100644 --- a/services/integration/reports.js +++ b/services/integration/reports.js @@ -111,6 +111,45 @@ const getUnscheduledIncidents = (startDate, endDate, memberId) => { }); }; +const getBookingChangeIncidents = (startDate, endDate, memberId) => { + const attributes = [ + 'id', + 'reservationId', + 'memberId', + 'resourceId', + 'oldBookingStart', + 'oldBookingEnd', + 'newBookingStart', + 'newBookingEnd', + 'incidentType', + 'chargeFee', + 'createdAt' + ]; + + const filters = {}; + + if (startDate && endDate) { + filters.createdAt = { + [Op.and]: { + [Op.gte]: startDate.toISOString(), + [Op.lte]: endDate.toISOString(), + } + } + } + + if (memberId){ + filters.memberId = memberId; + } + + return db.bookingChangeIncident.findAll({ + attributes, + where: filters, + sort: [ + ['createdAt', 'ASC'] + ] + }); +}; + const formatTime = (timestamp) => { const momentObject = moment.tz(timestamp, UI_TIMEZONE); if (momentObject.isValid()){ @@ -120,13 +159,13 @@ const formatTime = (timestamp) => { } }; -const getAllDoorLockIncidents = (dateRange, memberId) => { +const getAllIncidents = (dateRange, memberId) => { return new Promise ((resolve, reject) => { let startDate, endDate; if (dateRange.startDate && dateRange.endDate){ - startDate = moment.tz(dateRange.startDate, DEFAULT_DATE_FORMAT, UI_TIMEZONE); - endDate = moment.tz(dateRange.endDate, DEFAULT_DATE_FORMAT, UI_TIMEZONE); + startDate = moment.tz(dateRange.startDate, DEFAULT_DATE_FORMAT, UI_TIMEZONE).startOf('day'); + endDate = moment.tz(dateRange.endDate, DEFAULT_DATE_FORMAT, UI_TIMEZONE).endOf('day'); if (!startDate.isValid() || !endDate.isValid() || endDate.isBefore(startDate)){ reject(integrationServiceErrors.INVALID_DATE_RANGE); @@ -134,7 +173,14 @@ const getAllDoorLockIncidents = (dateRange, memberId) => { } } - const dataFetchJobs = [fetchAllMembers(), fetchOffices(), fetchResources(), getUnlockedIncidents(startDate, endDate, memberId), getUnscheduledIncidents(startDate, endDate, memberId)]; + const dataFetchJobs = [ + fetchAllMembers(), + fetchOffices(), + fetchResources(), + getUnlockedIncidents(startDate, endDate, memberId), + getUnscheduledIncidents(startDate, endDate, memberId), + getBookingChangeIncidents(startDate, endDate, memberId) + ]; Promise.all(dataFetchJobs) .then((data) => { @@ -143,6 +189,7 @@ const getAllDoorLockIncidents = (dateRange, memberId) => { const resources = data[2]; const unlockedIncidents = data[3]; const unscheduledIncidents = data[4]; + const bookingChangeIncidents = data[5]; const membersMap = {}; const officesMap = {}; @@ -200,6 +247,8 @@ const getAllDoorLockIncidents = (dateRange, memberId) => { }); }); + allIncidents.push(...bookingChangeIncidents); + resolve(allIncidents); }) .catch((error) => reject(error)); @@ -209,5 +258,5 @@ const getAllDoorLockIncidents = (dateRange, memberId) => { module.exports = { getUnlockedIncidents, getUnscheduledIncidents, - getAllDoorLockIncidents, + getAllIncidents, }; -- 2.47.3 From f6e8e6667b25f0180e8ad44b89c739e98002bdb8 Mon Sep 17 00:00:00 2001 From: Bilal Catic Date: Mon, 8 Jul 2019 20:23:50 +0200 Subject: [PATCH 4/4] display booking charge incidents; refactor frontend --- .../components/SingleIncidentsTable.js | 61 +++++++++++++++---- .../components/MemberIncidentsTables/index.js | 26 ++++++-- client/src/constants/constants.js | 47 ++++++++++++++ client/src/constants/enums.js | 10 +++ client/src/constants/menuItems.js | 13 ---- .../components/MemberSummary.js | 25 ++++++-- services/integration/reports.js | 33 +++++++++- 7 files changed, 179 insertions(+), 36 deletions(-) diff --git a/client/src/components/MemberIncidentsTables/components/SingleIncidentsTable.js b/client/src/components/MemberIncidentsTables/components/SingleIncidentsTable.js index 5c1d91e..98e778a 100644 --- a/client/src/components/MemberIncidentsTables/components/SingleIncidentsTable.js +++ b/client/src/components/MemberIncidentsTables/components/SingleIncidentsTable.js @@ -4,33 +4,48 @@ import ReactTable from 'react-table'; import 'react-table/react-table.css'; import { NavLink } from 'react-router-dom'; -import {incidentsReportHeaderTitles} from '../../../constants/menuItems'; +import {incidentsReportHeaderTitles} from '../../../constants/constants'; import { + incidentTableTypes, incidentDescriptions, incidentLevelDescriptions, UNLOCKED_INCIDENT_RELATED_WITH_RESERVATION, UNLOCKED_INCIDENT_STANDALONE, UNSCHEDULED_INCIDENT_AFTER_RESERVATION, UNSCHEDULED_INCIDENT_BEFORE_RESERVATION, UNSCHEDULED_INCIDENT_STANDALONE } from '../../../constants/enums'; +import { doorLockRelatedWithReservationIncidentHeaders, standaloneDoorLockIncidentHeaders, bookingChangeIncidentHeaders } from '../../../constants/constants'; const SingleIncidentsTable = props => { - const { loading, title, openMemberSummaryOnMemberClick, showBookingTimes, showDoorLockEntryTimes, hideMemberName } = props; + const { + loading, + title, + openMemberSummaryOnMemberClick, + hideMemberName, + tableType + } = props; const incidents = props.incidents ? props.incidents : []; - const columns = []; - if (incidents && incidents.length > 0){ - const incidentHeaders = Object.keys(incidentsReportHeaderTitles); - incidentHeaders.forEach((header) => { + if (incidents && incidents.length > 0){ + let tableHeaders; + switch (tableType) { + case incidentTableTypes.INCIDENTS_RELATED_TO_RESERVATIONS: + tableHeaders = doorLockRelatedWithReservationIncidentHeaders; + break; + case incidentTableTypes.STANDALONE_INCIDENTS: + tableHeaders = standaloneDoorLockIncidentHeaders; + break; + case incidentTableTypes.BOOKING_CHANGE_INCIDENTS: + tableHeaders = bookingChangeIncidentHeaders; + break; + default: + break; + } + + tableHeaders.forEach((header) => { const columnTitle = incidentsReportHeaderTitles[header]; let showColumn = true; - if ((header === 'bookingStart' || header === 'bookingEnd') && !showBookingTimes){ - showColumn = false; - } - if ((header === 'unlockTimestamp' || header === 'lockTimestamp') && !showDoorLockEntryTimes){ - showColumn = false; - } if (header === 'memberName' && hideMemberName){ showColumn = false; } @@ -55,6 +70,16 @@ const SingleIncidentsTable = props => { urlValue = `/practice-summary-report/${memberId}`; cellValue = props.value; break; + case 'reservation': + const bookingStart = props.row['_original'].bookingStart; + const bookingEnd = props.row['_original'].bookingEnd; + cellValue = `${bookingStart}\n${bookingEnd}`; + break; + case 'doorLockTimestamps': + const unlockTimestamp = props.row['_original'].unlockTimestamp; + const lockTimestamp = props.row['_original'].lockTimestamp; + cellValue = `${unlockTimestamp ? unlockTimestamp : '---'}\n${lockTimestamp ? lockTimestamp : '---'}`; + break; case 'incidentType': cellValue = incidentDescriptions[props.value]; break; @@ -85,6 +110,16 @@ const SingleIncidentsTable = props => { cellValue = `$ ${totalFeeFormatted}`; columnContentsAlignment = columnAlignments.right; break; + case 'oldReservation': + const oldBookingStart = props.row['_original'].oldBookingStart; + const oldBookingEnd = props.row['_original'].oldBookingEnd; + cellValue = `${oldBookingStart}\n${oldBookingEnd}`; + break; + case 'newReservation': + const newBookingStart = props.row['_original'].newBookingStart; + const newBookingEnd = props.row['_original'].newBookingEnd; + cellValue = `${newBookingStart}\n${newBookingEnd}`; + break; default: cellValue = props.value; } @@ -92,7 +127,7 @@ const SingleIncidentsTable = props => { if (openMemberSummaryOnMemberClick && urlValue){ return {cellValue} }else{ - return
{cellValue}
+ return
{cellValue}
} } }); diff --git a/client/src/components/MemberIncidentsTables/index.js b/client/src/components/MemberIncidentsTables/index.js index ed05541..a4d7c67 100644 --- a/client/src/components/MemberIncidentsTables/index.js +++ b/client/src/components/MemberIncidentsTables/index.js @@ -3,7 +3,8 @@ import {Accordion, Label} from 'semantic-ui-react'; import SingleIncidentsTable from './components/SingleIncidentsTable'; import { UNLOCKED_INCIDENT_RELATED_WITH_RESERVATION, UNLOCKED_INCIDENT_STANDALONE, UNSCHEDULED_INCIDENT_AFTER_RESERVATION, - UNSCHEDULED_INCIDENT_BEFORE_RESERVATION, UNSCHEDULED_INCIDENT_STANDALONE + UNSCHEDULED_INCIDENT_BEFORE_RESERVATION, UNSCHEDULED_INCIDENT_STANDALONE, BOOKING_MOVED_TO_ANOTHER_DAY, BOOKING_SHORTENED, + incidentTableTypes } from '../../constants/enums'; export default function MemberIncidentsTables (props) { @@ -11,6 +12,7 @@ export default function MemberIncidentsTables (props) { const incidentsRelatedToReservations = []; const standaloneIncidents = []; + const bookingChangeIncidents = []; if (Array.isArray(incidents)){ incidents.forEach((incident) => { @@ -25,6 +27,12 @@ export default function MemberIncidentsTables (props) { case UNSCHEDULED_INCIDENT_STANDALONE: standaloneIncidents.push(incident); break; + case BOOKING_MOVED_TO_ANOTHER_DAY: + case BOOKING_SHORTENED: + bookingChangeIncidents.push(incident); + break; + default: + break; } } }); @@ -35,8 +43,8 @@ export default function MemberIncidentsTables (props) { loading={pendingIncidents} incidents={incidentsRelatedToReservations} openMemberSummaryOnMemberClick - showBookingTimes hideMemberName={hideMemberName} + tableType={incidentTableTypes.INCIDENTS_RELATED_TO_RESERVATIONS} /> ); @@ -45,8 +53,17 @@ export default function MemberIncidentsTables (props) { loading={pendingIncidents} incidents={standaloneIncidents} openMemberSummaryOnMemberClick - showDoorLockEntryTimes hideMemberName={hideMemberName} + tableType={incidentTableTypes.STANDALONE_INCIDENTS} + /> + ); + + const bookingChangeIncidentsTable = ( + ); @@ -63,7 +80,8 @@ export default function MemberIncidentsTables (props) { }, { key: 'reservation-modification-incidents', - title: {content: