detect and save unscheduled and unlocked incidents
This commit is contained in:
@@ -1,2 +1,2 @@
|
||||
export * from './doorLockActions';
|
||||
export * from './officeRnDActions';
|
||||
export * from './integrationActions';
|
||||
|
||||
@@ -11,7 +11,7 @@ import API from '../../utilities/api';
|
||||
|
||||
export const fetchMappings = (dispatch) => {
|
||||
dispatch({type: FETCH_MAPPINGS_PENDING});
|
||||
API.get('doorLock/mappings')
|
||||
API.get('integration/mappings')
|
||||
.then(response => {
|
||||
dispatch({type: FETCH_MAPPINGS_SUCCESS, payload: response.data});
|
||||
})
|
||||
@@ -22,7 +22,7 @@ export const fetchMappings = (dispatch) => {
|
||||
|
||||
export const addNewMapping = (dispatch, mapping) => {
|
||||
dispatch({type: ADD_NEW_MAPPING_PENDING});
|
||||
API.post('doorLock/mappings', {
|
||||
API.post('integration/mappings', {
|
||||
mapping
|
||||
})
|
||||
.then(response => {
|
||||
@@ -3,8 +3,9 @@
|
||||
"username": "docker",
|
||||
"password": "docker",
|
||||
"database": "CrmIntegration",
|
||||
"port": "5432",
|
||||
"dialect": "postgres"
|
||||
"port": "5431",
|
||||
"dialect": "postgres",
|
||||
"logging": false
|
||||
},
|
||||
"test": {
|
||||
"use_env_variable": "DATABASE_URL"
|
||||
|
||||
@@ -4,22 +4,64 @@ const DISABLE_PASSAGE_MODE = 'Disable Passage Mode by Group 2';
|
||||
|
||||
const VALID_CSV_HEADERS = ['Date', 'Time', 'User No', 'Name', 'Event'];
|
||||
|
||||
|
||||
const doorLockEvents = {
|
||||
USER_LOCKED: 'locked',
|
||||
USER_UNLOCKED: 'unlocked',
|
||||
};
|
||||
const doorChargeTypes = {
|
||||
LEFT_UNLOCKED: 'unlocked',
|
||||
UNSCHEDULED_USE: 'unscheduled'
|
||||
const unlockedIncidentLevelsPrices = {
|
||||
UNLOCKED_0: {
|
||||
id: 0,
|
||||
title: 'UNLOCKED_0',
|
||||
price: parseInt(process.env.UNLOCK_0) || 0
|
||||
},
|
||||
UNLOCKED_1: {
|
||||
id: 1,
|
||||
title: 'UNLOCKED_1',
|
||||
price: parseInt(process.env.UNLOCK_1) || 10
|
||||
},
|
||||
UNLOCKED_2: {
|
||||
id: 2,
|
||||
title: 'UNLOCKED_2',
|
||||
price: parseInt(process.env.UNLOCK_2) || 20
|
||||
},
|
||||
UNLOCKED_3: {
|
||||
id: 3,
|
||||
title: 'UNLOCKED_3',
|
||||
price: parseInt(process.env.UNLOCK_3) || 30
|
||||
},
|
||||
UNLOCKED_4: {
|
||||
id: 4,
|
||||
title: 'UNLOCKED_4',
|
||||
price: parseInt(process.env.UNLOCK_4) || 40
|
||||
},
|
||||
UNLOCKED_5: {
|
||||
id: 5,
|
||||
title: 'UNLOCKED_5',
|
||||
price: parseInt(process.env.UNLOCK_5) || 50
|
||||
}
|
||||
};
|
||||
const csvParserErrors = {
|
||||
INVALID_HEADERS: 'Invalid headers',
|
||||
INVALID_ENTRY_EXPECTED_USER: 'Invalid entry type. Expected user entry type',
|
||||
INVALID_ENTRY_EXPECTED_PASSAGE_MODE: 'Invalid entry type. Expected enable/disable passage mode following user entry',
|
||||
UNKNOWN_MEMBER: 'Member is not registered in OfficeRnD system',
|
||||
GENERIC_ERROR: 'There was error while parsing uploaded file(s)',
|
||||
};
|
||||
const officeRnDAPIErrors = {
|
||||
FAILED_TO_FETCH_MEMBERS: 'Failed to fetch members',
|
||||
FAILED_TO_FETCH_BOOKINGS: 'Failed to fetch booking reservations'
|
||||
};
|
||||
const integrationServiceErrors = {
|
||||
FAILED_TO_SAVE_BOOKINGS: 'Failed to save booking reservations',
|
||||
FAILED_TO_SAVE_DOOR_LOCK_ENTRIES: 'Failed to save door lock entries',
|
||||
FAILED_TO_SAVE_DATA_GENERIC: 'Failed to save data',
|
||||
};
|
||||
|
||||
const incidentType = {
|
||||
NOT_AN_INCIDENT: 1,
|
||||
UNLOCKED_INCIDENT: 2,
|
||||
UNSCHEDULED_INCIDENT: 3,
|
||||
};
|
||||
|
||||
module.exports = {
|
||||
@@ -30,5 +72,7 @@ module.exports = {
|
||||
csvParserErrors,
|
||||
officeRnDAPIErrors,
|
||||
doorLockEvents,
|
||||
doorChargeTypes,
|
||||
unlockedIncidentLevelsPrices,
|
||||
integrationServiceErrors,
|
||||
incidentType,
|
||||
};
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
'use strict';
|
||||
|
||||
const { getMappingsFromDatabase, fetchOffices, fetchResources, saveNewMappingToDatabase } = require('../services/officeRnD/resources');
|
||||
const { parseDoorLockDataFile, writeDoorLockEvent } = require('../services/doorLock');
|
||||
const { parseDoorLockDataFile, writeDoorLockEvent } = require('../services/doorLock/doorLock');
|
||||
const { fetchAllBookings, writeBookingReservation } = require('../services/officeRnD/bookings');
|
||||
const { officeRnDAPIErrors } = require('../constants/constants');
|
||||
const { calculateDoorLockCharges } = require('../services/integration/doorLockCharges');
|
||||
const { integrationServiceErrors } = require('../constants/constants');
|
||||
|
||||
const IncomingForm = require('formidable').IncomingForm;
|
||||
|
||||
@@ -30,64 +30,42 @@ const uploadDoorLockData = (req, res) => {
|
||||
unknownMembers.push(...parserResult.unknownMembers);
|
||||
});
|
||||
|
||||
res.json({
|
||||
parsedData,
|
||||
parserErrors,
|
||||
unknownMembers
|
||||
});
|
||||
const asyncJobs = [];
|
||||
|
||||
fetchAllBookings()
|
||||
.then((bookingEntries) => {
|
||||
bookingEntries.forEach((bookingEntry) => writeBookingReservation(bookingEntry));
|
||||
bookingEntries.forEach((bookingEntry) => asyncJobs.push(writeBookingReservation(bookingEntry)));
|
||||
})
|
||||
.catch((error) => {
|
||||
console.log('===> ERROR');
|
||||
console.log(error);
|
||||
res.status(500).send(error);
|
||||
return;
|
||||
});
|
||||
|
||||
parsedData.forEach((entry) => {
|
||||
writeDoorLockEvent(entry);
|
||||
});
|
||||
parsedData.forEach((entry) => asyncJobs.push(writeDoorLockEvent(entry)));
|
||||
|
||||
Promise.all(asyncJobs)
|
||||
.then(() => {
|
||||
res.json({
|
||||
parsedData,
|
||||
parserErrors,
|
||||
unknownMembers
|
||||
});
|
||||
|
||||
calculateDoorLockCharges();
|
||||
})
|
||||
.catch((error) => {
|
||||
console.log(`${integrationServiceErrors.FAILED_TO_SAVE_BOOKINGS} or ${integrationServiceErrors.FAILED_TO_SAVE_DOOR_LOCK_ENTRIES}`)
|
||||
console.log(error);
|
||||
res.status(500).send(integrationServiceErrors.FAILED_TO_SAVE_DATA_GENERIC);
|
||||
});
|
||||
})
|
||||
.catch((error) => {
|
||||
res.status(500).send(officeRnDAPIErrors.FAILED_TO_FETCH_MEMBERS);
|
||||
res.status(500).send(error);
|
||||
});
|
||||
});
|
||||
|
||||
form.parse(req);
|
||||
};
|
||||
|
||||
const getKnownOfficeResourceMappings = (req, res) => {
|
||||
const dataToFetch = [getMappingsFromDatabase(), fetchOffices(), fetchResources() ];
|
||||
|
||||
Promise.all(dataToFetch)
|
||||
.then(result => {
|
||||
res.send({
|
||||
existingMappings: result[0],
|
||||
offices: result[1],
|
||||
resources: result[2],
|
||||
});
|
||||
})
|
||||
.catch(error => {
|
||||
res.status(500).send();
|
||||
});
|
||||
};
|
||||
|
||||
const addNewMapping = (req, res) => {
|
||||
const newMapping = req.body && req.body.mapping ? req.body.mapping : null;
|
||||
if (newMapping && newMapping.officeSlug && newMapping.resourceSlug && newMapping.officeId && newMapping.resourceId){
|
||||
saveNewMappingToDatabase(newMapping)
|
||||
.then(result => {
|
||||
res.send(newMapping);
|
||||
})
|
||||
.catch(error => {
|
||||
res.status(500).send(error);
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
module.exports = {
|
||||
uploadDoorLockData,
|
||||
getKnownOfficeResourceMappings,
|
||||
addNewMapping,
|
||||
};
|
||||
|
||||
37
controllers/integration.js
Normal file
37
controllers/integration.js
Normal file
@@ -0,0 +1,37 @@
|
||||
'use strict';
|
||||
|
||||
const { getMappingsFromDatabase, fetchOffices, fetchResources, saveNewMappingToDatabase } = require('../services/officeRnD/resources');
|
||||
|
||||
const getKnownOfficeResourceMappings = (req, res) => {
|
||||
const dataToFetch = [getMappingsFromDatabase(), fetchOffices(), fetchResources() ];
|
||||
|
||||
Promise.all(dataToFetch)
|
||||
.then(result => {
|
||||
res.send({
|
||||
existingMappings: result[0],
|
||||
offices: result[1],
|
||||
resources: result[2],
|
||||
});
|
||||
})
|
||||
.catch(error => {
|
||||
res.status(500).send();
|
||||
});
|
||||
};
|
||||
|
||||
const addNewMapping = (req, res) => {
|
||||
const newMapping = req.body && req.body.mapping ? req.body.mapping : null;
|
||||
if (newMapping && newMapping.officeSlug && newMapping.resourceSlug && newMapping.officeId && newMapping.resourceId){
|
||||
saveNewMappingToDatabase(newMapping)
|
||||
.then(() => {
|
||||
res.send(newMapping);
|
||||
})
|
||||
.catch(error => {
|
||||
res.status(500).send(error);
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
module.exports = {
|
||||
getKnownOfficeResourceMappings,
|
||||
addNewMapping,
|
||||
};
|
||||
@@ -1,2 +1,18 @@
|
||||
BASIC_AUTH_USERNAME=username
|
||||
BASIC_AUTH_PASSWORD=password
|
||||
|
||||
OFFICE_RnD_TOKEN=token for Office RnD API requests
|
||||
MAX_BACK_TO_BACK_DIFFERENCE=Time in minutes
|
||||
EARLIEST_UNLOCK=2
|
||||
|
||||
UNSCHEDULED_USE_TIME_RESOLUTION=Time in minutes
|
||||
UNSCHEDULED_USE_CHARGE_FEE=Charge fee
|
||||
|
||||
UNLOCK_0=Price for unlocked door, first month
|
||||
UNLOCK_1=Price for unlocked door, second month
|
||||
UNLOCK_2=Price for unlocked door, third month
|
||||
UNLOCK_3=Price for unlocked door, fourth month
|
||||
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
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
'use strict';
|
||||
|
||||
module.exports = {
|
||||
up: (queryInterface, Sequelize) => {
|
||||
return queryInterface.addColumn('bookingReservations', 'officeId', {
|
||||
type: Sequelize.TEXT,
|
||||
after: 'memberId',
|
||||
});
|
||||
},
|
||||
|
||||
down: (queryInterface, Sequelize) => {
|
||||
return queryInterface.removeColumn('bookingReservations', 'officeId');
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,11 @@
|
||||
'use strict';
|
||||
|
||||
module.exports = {
|
||||
up: (queryInterface, Sequelize) => {
|
||||
return queryInterface.renameTable('doorLockIncidents', 'unscheduledIncidents');
|
||||
},
|
||||
|
||||
down: (queryInterface, Sequelize) => {
|
||||
return queryInterface.renameTable('unscheduledIncidents', 'doorLockIncidents');
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,35 @@
|
||||
'use strict';
|
||||
|
||||
module.exports = {
|
||||
up: (queryInterface, Sequelize) => {
|
||||
return queryInterface.sequelize.transaction((t) => {
|
||||
return Promise.all([
|
||||
queryInterface.removeColumn('unscheduledIncidents', 'chargeType'),
|
||||
queryInterface.addColumn('unscheduledIncidents', 'chargePrice', {
|
||||
type: Sequelize.FLOAT,
|
||||
after: 'doorLockEventTimestamp'
|
||||
}),
|
||||
queryInterface.addColumn('unscheduledIncidents', 'timeIntervalsToCharge', {
|
||||
type: Sequelize.INTEGER,
|
||||
after: 'chargePrice'
|
||||
}),
|
||||
queryInterface.renameColumn('unscheduledIncidents', 'chargeFee', 'totalChargeFee')
|
||||
]);
|
||||
});
|
||||
},
|
||||
|
||||
down: (queryInterface, Sequelize) => {
|
||||
return queryInterface.sequelize.transaction((t) => {
|
||||
return Promise.all([
|
||||
queryInterface.renameColumn('unscheduledIncidents', 'totalChargeFee', 'chargeFee'),
|
||||
queryInterface.removeColumn('unscheduledIncidents', 'timeIntervalsToCharge'),
|
||||
queryInterface.removeColumn('unscheduledIncidents', 'chargePrice'),
|
||||
queryInterface.addColumn('unscheduledIncidents', 'chargeType', {
|
||||
type: Sequelize.ENUM,
|
||||
values: ['unlocked', 'unscheduled'],
|
||||
after: 'doorLockEventTimestamp'
|
||||
}),
|
||||
]);
|
||||
});
|
||||
}
|
||||
};
|
||||
35
migrations/20190612125150-add-unlocked-incidents-table.js
Normal file
35
migrations/20190612125150-add-unlocked-incidents-table.js
Normal file
@@ -0,0 +1,35 @@
|
||||
'use strict';
|
||||
|
||||
module.exports = {
|
||||
up: (queryInterface, Sequelize) => {
|
||||
return queryInterface.createTable('unlockedIncidents', {
|
||||
id: {
|
||||
allowNull: false,
|
||||
autoIncrement: true,
|
||||
primaryKey: true,
|
||||
type: Sequelize.INTEGER
|
||||
},
|
||||
reservationId: Sequelize.TEXT,
|
||||
memberId: Sequelize.TEXT,
|
||||
resourceId: Sequelize.TEXT,
|
||||
bookingStart: Sequelize.DATE,
|
||||
bookingEnd: Sequelize.DATE,
|
||||
incidentLevel: {
|
||||
type: Sequelize.ENUM,
|
||||
values: ['UNLOCKED_0', 'UNLOCKED_1', 'UNLOCKED_2', 'UNLOCKED_3', 'UNLOCKED_4', 'UNLOCKED_5']
|
||||
},
|
||||
incidentLevelPrice: Sequelize.FLOAT,
|
||||
createdAt: {
|
||||
allowNull: false,
|
||||
type: Sequelize.DATE
|
||||
},
|
||||
updatedAt: {
|
||||
allowNull: false,
|
||||
type: Sequelize.DATE
|
||||
}
|
||||
});
|
||||
},
|
||||
down: (queryInterface, Sequelize) => {
|
||||
return queryInterface.dropTable('doorLockIncidents');
|
||||
}
|
||||
};
|
||||
@@ -4,6 +4,7 @@ module.exports = (sequelize, DataTypes) => {
|
||||
const bookingReservation = sequelize.define('bookingReservation', {
|
||||
reservationId: DataTypes.TEXT,
|
||||
memberId: DataTypes.TEXT,
|
||||
officeId: DataTypes.TEXT,
|
||||
resourceId: DataTypes.TEXT,
|
||||
start: DataTypes.DATE,
|
||||
end: DataTypes.DATE,
|
||||
|
||||
29
models/unlockedIncident.js
Normal file
29
models/unlockedIncident.js
Normal file
@@ -0,0 +1,29 @@
|
||||
'use strict';
|
||||
|
||||
const { unlockedIncidentLevelsPrices } = require('../constants/constants');
|
||||
|
||||
module.exports = (sequelize, DataTypes) => {
|
||||
const unlockedIncident = sequelize.define('unlockedIncident', {
|
||||
reservationId: DataTypes.TEXT,
|
||||
memberId: DataTypes.TEXT,
|
||||
resourceId: DataTypes.TEXT,
|
||||
bookingStart: DataTypes.DATE,
|
||||
bookingEnd: DataTypes.DATE,
|
||||
incidentLevel: {
|
||||
type: DataTypes.ENUM,
|
||||
values: [
|
||||
unlockedIncidentLevelsPrices.UNLOCKED_0.title,
|
||||
unlockedIncidentLevelsPrices.UNLOCKED_1.title,
|
||||
unlockedIncidentLevelsPrices.UNLOCKED_2.title,
|
||||
unlockedIncidentLevelsPrices.UNLOCKED_3.title,
|
||||
unlockedIncidentLevelsPrices.UNLOCKED_4.title,
|
||||
unlockedIncidentLevelsPrices.UNLOCKED_5.title,
|
||||
]
|
||||
},
|
||||
incidentLevelPrice: DataTypes.FLOAT,
|
||||
}, {});
|
||||
unlockedIncident.associate = function(models) {
|
||||
// associations can be defined here
|
||||
};
|
||||
return unlockedIncident;
|
||||
};
|
||||
@@ -1,9 +1,9 @@
|
||||
'use strict';
|
||||
|
||||
const { doorLockEvents, doorChargeTypes } = require('../constants/constants');
|
||||
const { doorLockEvents } = require('../constants/constants');
|
||||
|
||||
module.exports = (sequelize, DataTypes) => {
|
||||
const doorLockIncident = sequelize.define('doorLockIncident', {
|
||||
const unscheduledIncident = sequelize.define('unscheduledIncident', {
|
||||
reservationId: DataTypes.TEXT,
|
||||
memberId: DataTypes.TEXT,
|
||||
resourceId: DataTypes.TEXT,
|
||||
@@ -14,14 +14,12 @@ module.exports = (sequelize, DataTypes) => {
|
||||
type: DataTypes.ENUM,
|
||||
values: [doorLockEvents.USER_LOCKED, doorLockEvents.USER_UNLOCKED]
|
||||
},
|
||||
chargeType: {
|
||||
type: DataTypes.ENUM,
|
||||
values: [doorChargeTypes.LEFT_UNLOCKED, doorChargeTypes.UNSCHEDULED_USE]
|
||||
},
|
||||
chargeFee: DataTypes.FLOAT,
|
||||
chargePrice: DataTypes.FLOAT,
|
||||
timeIntervalsToCharge: DataTypes.INTEGER,
|
||||
totalChargeFee: DataTypes.FLOAT,
|
||||
}, {});
|
||||
doorLockIncident.associate = function(models) {
|
||||
unscheduledIncident.associate = function(models) {
|
||||
// associations can be defined here
|
||||
};
|
||||
return doorLockIncident;
|
||||
return unscheduledIncident;
|
||||
};
|
||||
@@ -8,7 +8,7 @@
|
||||
"install-server": "npm install",
|
||||
"install-client": "cd client && yarn install",
|
||||
"docker-build": "docker build -t simaspace .",
|
||||
"docker-start": "docker run -e POSTGRES_USER=docker -e POSTGRES_PASSWORD=docker -e POSTGRES_DB=CrmIntegration --name pg_simaspace -d -p 5432:5432 simaspace",
|
||||
"docker-start": "docker run -e POSTGRES_USER=docker -e POSTGRES_PASSWORD=docker -e POSTGRES_DB=CrmIntegration --name pg_simaspace -d -p 5431:5432 simaspace",
|
||||
"docker-stop": "docker stop pg_simaspace",
|
||||
"setup": "npm run install-server && npm run install-client && npm run docker-build && npm run docker-start && sleep 5 && npm run migrate",
|
||||
"migrate": "npx sequelize db:migrate",
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
'use strict';
|
||||
|
||||
const { apiStatusCheck } = require('../controllers/apiStatusCheck');
|
||||
const { uploadDoorLockData, getKnownOfficeResourceMappings, addNewMapping } = require('../controllers/doorLock');
|
||||
const { uploadDoorLockData } = require('../controllers/doorLock');
|
||||
const { getKnownOfficeResourceMappings, addNewMapping } = require('../controllers/integration');
|
||||
const { calculateDoorLockCharges } = require('../services/integration/doorLockCharges');
|
||||
|
||||
const express = require('express');
|
||||
const router = express.Router();
|
||||
@@ -9,7 +11,10 @@ const router = express.Router();
|
||||
router.get('/', apiStatusCheck);
|
||||
|
||||
router.post('/doorLock/upload', uploadDoorLockData);
|
||||
router.get('/doorLock/mappings', getKnownOfficeResourceMappings);
|
||||
router.post('/doorLock/mappings', addNewMapping);
|
||||
router.get('/integration/mappings', getKnownOfficeResourceMappings);
|
||||
router.post('/integration/mappings', addNewMapping);
|
||||
|
||||
// temporary route, manually trigger door lock charge calculations
|
||||
router.get('/calculate', (req, res) => { calculateDoorLockCharges(); res.send();});
|
||||
|
||||
module.exports = router;
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
'use strict';
|
||||
|
||||
const db = require('../models/index');
|
||||
const db = require('../../models');
|
||||
const fs = require('fs');
|
||||
const csv = require('csv-parser');
|
||||
const moment = require('moment');
|
||||
const moment = require('moment/moment');
|
||||
const Op = require('sequelize').Op;
|
||||
|
||||
const {
|
||||
USER_ENTRY_EVENT,
|
||||
@@ -12,10 +13,10 @@ const {
|
||||
VALID_CSV_HEADERS,
|
||||
doorLockEvents,
|
||||
csvParserErrors,
|
||||
} = require('../constants/constants');
|
||||
} = require('../../constants/constants');
|
||||
|
||||
const { fetchAllMembers, findMember } = require('../services/officeRnD/members');
|
||||
const { getMappingsFromDatabase } = require('../services/officeRnD/resources');
|
||||
const { fetchAllMembers, findMember } = require('../officeRnD/members');
|
||||
const { getMappingsFromDatabase } = require('../officeRnD/resources');
|
||||
|
||||
const extractMappingFromFileName = (fileName) => {
|
||||
const contentBetweenBracketsRegex = /\[(.*?)\]/;
|
||||
@@ -170,13 +171,60 @@ const parseDoorLockDataFile = (file) => {
|
||||
};
|
||||
|
||||
const writeDoorLockEvent = (entry) => {
|
||||
console.log('Write entry : ');
|
||||
console.log(entry);
|
||||
console.log('====');
|
||||
return db.doorLockEvent.findOrCreate({where: {...entry}, defaults: {...entry}});
|
||||
};
|
||||
|
||||
const getUnlockEntryForReservation = (reservation) => {
|
||||
const { start, end, memberId, resourceId } = reservation;
|
||||
|
||||
const attributes = ['memberName', 'event', 'timestamp'];
|
||||
const earliestUnlock = parseInt(process.env.EARLIEST_UNLOCK) || 0;
|
||||
const fromTimestamp = moment(start).subtract(earliestUnlock).toISOString();
|
||||
const toTimestamp = end;
|
||||
|
||||
const filters = {
|
||||
memberId,
|
||||
timestamp: {
|
||||
[Op.and]: [
|
||||
{[Op.gt]: fromTimestamp},
|
||||
{[Op.lte]: toTimestamp}
|
||||
]
|
||||
},
|
||||
event: doorLockEvents.USER_UNLOCKED,
|
||||
resourceId,
|
||||
};
|
||||
|
||||
return db.doorLockEvent.findOne({
|
||||
attributes,
|
||||
where: filters,
|
||||
})
|
||||
|
||||
};
|
||||
|
||||
const getRelatedDoorLockEntries = (fromTimestamp, toTimestamp, memberId, resourceId) => {
|
||||
const attributes = ['memberName', 'event', 'timestamp'];
|
||||
|
||||
const filters = {
|
||||
memberId,
|
||||
timestamp: {
|
||||
[Op.and]: [
|
||||
{[Op.gt]: fromTimestamp},
|
||||
{[Op.lte]: toTimestamp}
|
||||
]
|
||||
},
|
||||
event: doorLockEvents.USER_LOCKED,
|
||||
resourceId,
|
||||
};
|
||||
|
||||
return db.doorLockEvent.findOne({
|
||||
attributes,
|
||||
where: filters
|
||||
})
|
||||
};
|
||||
|
||||
module.exports = {
|
||||
parseDoorLockDataFile,
|
||||
writeDoorLockEvent,
|
||||
getRelatedDoorLockEntries,
|
||||
getUnlockEntryForReservation,
|
||||
};
|
||||
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
|
||||
};
|
||||
@@ -1,8 +1,11 @@
|
||||
'use strict';
|
||||
|
||||
const db = require('../../models/index');
|
||||
const moment = require('moment-timezone');
|
||||
const Op = require('sequelize').Op;
|
||||
|
||||
const { API } = require('../../helpers/api');
|
||||
const { officeRnDAPIErrors } = require('../../constants/constants');
|
||||
|
||||
const fetchAllBookings = () => {
|
||||
return new Promise((resolve, reject) => {
|
||||
@@ -15,19 +18,110 @@ const fetchAllBookings = () => {
|
||||
cleanedBookingReservations.push({
|
||||
reservationId: fullBookingEntry['_id'],
|
||||
memberId: fullBookingEntry.member,
|
||||
officeId: fullBookingEntry.office,
|
||||
resourceId: fullBookingEntry.resourceId,
|
||||
start: fullBookingEntry.start.dateTime,
|
||||
end: fullBookingEntry.end.dateTime,
|
||||
timezone: fullBookingEntry.timezone,
|
||||
canceled: fullBookingEntry.canceled || false,
|
||||
});
|
||||
});
|
||||
resolve(cleanedBookingReservations);
|
||||
})
|
||||
.catch((error) => {
|
||||
reject(error);
|
||||
console.log(officeRnDAPIErrors.FAILED_TO_FETCH_BOOKINGS);
|
||||
console.log('Details : ', error);
|
||||
reject(officeRnDAPIErrors.FAILED_TO_FETCH_BOOKINGS);
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
const getAllFinishedBookings = () => {
|
||||
const attributes = ['reservationId', 'memberId', 'resourceId', 'start', 'end', 'timezone'];
|
||||
const filters = {
|
||||
canceled: false,
|
||||
end: {
|
||||
[Op.lt]: moment().toISOString()
|
||||
}
|
||||
};
|
||||
|
||||
return db.bookingReservation.findAll({
|
||||
attributes,
|
||||
where: filters,
|
||||
order: [
|
||||
['start', 'ASC'],
|
||||
]
|
||||
})
|
||||
};
|
||||
|
||||
const getFirstNextBooking = (reservation) => {
|
||||
return new Promise ((resolve, reject) => {
|
||||
const {resourceId, start, timezone} = reservation;
|
||||
const endOfTheDay = moment.tz(start, timezone).endOf('Day').toISOString();
|
||||
|
||||
const attributes = ['reservationId', 'memberId', 'resourceId', 'start', 'end', 'timezone'];
|
||||
const filters = {
|
||||
canceled: false,
|
||||
start: {
|
||||
[Op.gt]: start
|
||||
},
|
||||
end: {
|
||||
[Op.lte]: endOfTheDay
|
||||
},
|
||||
resourceId,
|
||||
};
|
||||
const order = [['start', 'ASC']];
|
||||
|
||||
db.bookingReservation.findAll({
|
||||
attributes,
|
||||
where: filters,
|
||||
order,
|
||||
})
|
||||
.then((reservations) => {
|
||||
if (reservations && reservations[0]){
|
||||
resolve(reservations[0]);
|
||||
}else{
|
||||
resolve(undefined);
|
||||
}
|
||||
})
|
||||
.catch((error) => reject(error));
|
||||
});
|
||||
};
|
||||
|
||||
const getFirstPreviousBooking = (reservation) => {
|
||||
return new Promise ((resolve, reject) => {
|
||||
const {resourceId, start, timezone} = reservation;
|
||||
const startOfTheDay = moment.tz(start, timezone).startOf('Day').toISOString();
|
||||
|
||||
const attributes = ['reservationId', 'memberId', 'resourceId', 'start', 'end', 'timezone'];
|
||||
const filters = {
|
||||
canceled: false,
|
||||
start: {
|
||||
[Op.gte]: startOfTheDay
|
||||
},
|
||||
end: {
|
||||
[Op.lte]: start
|
||||
},
|
||||
resourceId,
|
||||
};
|
||||
const order = [['end', 'DESC']];
|
||||
|
||||
db.bookingReservation.findAll({
|
||||
attributes,
|
||||
where: filters,
|
||||
order,
|
||||
})
|
||||
.then((reservations) => {
|
||||
if (reservations && reservations[0]){
|
||||
resolve(reservations[0]);
|
||||
}else{
|
||||
resolve(undefined);
|
||||
}
|
||||
})
|
||||
.catch((error) => reject(error));
|
||||
});
|
||||
};
|
||||
|
||||
const writeBookingReservation = (bookingReservation) => {
|
||||
return db.bookingReservation.findOrCreate({where: {...bookingReservation}, defaults: {...bookingReservation}});
|
||||
};
|
||||
@@ -35,4 +129,7 @@ const writeBookingReservation = (bookingReservation) => {
|
||||
module.exports = {
|
||||
fetchAllBookings,
|
||||
writeBookingReservation,
|
||||
getAllFinishedBookings,
|
||||
getFirstNextBooking,
|
||||
getFirstPreviousBooking,
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user