Skip to content

Commit

Permalink
feature(approve-reject-booking): Approving and Rejecting Bookings
Browse files Browse the repository at this point in the history
- add statusId to booking field

- create route for filtering bookings by status

- create a patch route to approve/reject bookings

- create tests for this feature

[Finishes #169469640]
  • Loading branch information
nignanthomas committed Nov 8, 2019
1 parent e03599f commit 4874cb0
Show file tree
Hide file tree
Showing 32 changed files with 725 additions and 101 deletions.
132 changes: 66 additions & 66 deletions package-lock.json

Large diffs are not rendered by default.

95 changes: 87 additions & 8 deletions src/controllers/accommodationController.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ import checkDate from '../helpers/checkDateHelper';
import bookingHelper from '../helpers/bookingHelper';
import getAccommodation from '../helpers/getAccommodation';
import sendEmail from '../helpers/emailHelper';
import responseHelper from '../utils/responseHelper';
import notifSender from '../helpers/notifSender';

cloudinary.config({ CLOUDINARY_URL: process.env.CLOUDINARY_URL });

Expand All @@ -27,6 +29,8 @@ const {
ACTIVATED,
DEACTIVATED,
DEACTIVATED_ACCOMMODATIONS,
NO_BOOKING,
FOUND_BOOKINGS,
} = strings.accommodation.success;

export default class AccommodationController {
Expand Down Expand Up @@ -113,9 +117,8 @@ export default class AccommodationController {
where: {
userId: req.user.payload.id
},
attributes: { exclude: ['accommodationId', 'userId'] },
include: [{ association: 'accommodation', attributes: ['id', 'name', 'description', 'cost', 'currency', 'owner', 'images'] },
{ association: 'user', attributes: ['id', 'username', 'email'] }],
attributes: { exclude: ['accommodationId', 'userId', 'statusId'] },
include: bookingHelper.bookingAssociations,
}).then(book => responseUtil(res, 200, BOOKED_FOUND, book));
}

Expand All @@ -124,6 +127,8 @@ export default class AccommodationController {
checkInDate, checkOutDate, accomodationId, roomsNumber
} = req.body;

const APP_URL_BACKEND = `${req.protocol}://${req.headers.host}`;

bookingHelper.findAccomodation(req).then(bookings => {
if (!bookings) {
return responseUtil(res, 404, strings.accommodations.error.ACCOMMODATION_NOT_FOUND);
Expand All @@ -142,18 +147,23 @@ export default class AccommodationController {
checkIn: checkInDate,
checkOut: checkOutDate,
};
bookingHelper.findBooked(req, accommodation[0].id).then(booked => {
bookingHelper.findBooked(req, accommodation[0].id).then(async booked => {
if (booked.length === 0) {

if (accommodation[0].availableSpace < roomsNumber) {

return responseUtil(res, 400, strings.accommodation.error.EXCEED_NUMBER);
}
models.booking.create(bookingData);

const remainingSpace = parseInt(accommodation[0].availableSpace) - roomsNumber;
const newBooking = await models.booking.create(bookingData);

bookingHelper.updateAccomodation(req, remainingSpace);
await notifSender(
'Accommodation Booked',
newBooking,
accommodation[0].owner,
APP_URL_BACKEND,
'placed',
'booking'
);

return responseUtil(res, 200, strings.accommodation.success.SUCCESSFUL_BOOKED);
}
Expand Down Expand Up @@ -218,4 +228,73 @@ export default class AccommodationController {
}
return responseUtil(res, 403, strings.users.error.TRAVEL_ADMINS_ONLY);
}

static async searchBookings(req, res) {
const bookings = await bookingHelper.findBookings(req.body);
let myBookings = [];
if (bookings.length) {
bookingHelper.filterOwner(bookings, myBookings, req.user.payload.id);
} else {
myBookings = bookings;
}
return responseUtil(res, (!myBookings.length) ? 404 : 200, (!myBookings.length)
? NO_BOOKING : FOUND_BOOKINGS, myBookings);
}

static async changeStatus(req, res) {
const { id } = req.params;
const {
statusId, activity, subject, responseMessage, actionIsApprove
} = req;

const bookingToProcess = await bookingHelper.findOneBooking({ id });
const { accommodationId } = bookingToProcess;

const accommodation = await bookingHelper.findOneAccommodation({ id: accommodationId });

if (accommodation.owner !== req.user.payload.id) {
return responseHelper(res, strings.accommodations.error.NOT_OWNER, null, 403);
}

const APP_URL_BACKEND = `${req.protocol}://${req.headers.host}`;

if (bookingToProcess) {
let booking = await models.booking.update({ statusId }, { where: { id }, returning: true, });

booking = booking[1][0].dataValues;

if (actionIsApprove) {
const remainingSpace = accommodation.availableSpace - bookingToProcess.bookedSpace;
req.body.accomodationId = accommodation.id;
await bookingHelper.updateAccomodation(req, remainingSpace);
}

await notifSender(subject, booking, booking.userId, APP_URL_BACKEND, activity, 'booking');
return responseHelper(res, responseMessage, null, 200);
}
return responseHelper(res, strings.accommodations.error.BOOKING_NOT_FOUND, null, 404);
}

static async viewOneBooking(req, res) {
const { id } = req.params;
const { role } = req.user.payload;
models.booking.findOne({
where: {
id,
},
attributes: { exclude: ['accommodationId', 'userId', 'statusId'] },
include: bookingHelper.bookingAssociations,
}).then(booking => {
if (!booking) {
return responseUtil(res, 404, NO_BOOKING);
}
// eslint-disable-next-line max-len
if ((role === 5 && booking.accommodation.owner !== req.user.payload.id) || (role !== 5 && booking.user.id !== req.user.payload.id)) {
return responseUtil(res, 404, NO_BOOKING);
}

return responseUtil(res, 200, BOOKED_FOUND, booking);
});

}
}
2 changes: 1 addition & 1 deletion src/controllers/destinationController.js
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ export default class destinationController {

const { lineManager } = user.payload;

await notifSender('Request Created', request, lineManager, APP_URL_BACKEND, 'created');
await notifSender('Request Created', request, lineManager, APP_URL_BACKEND, 'created', 'request');

return Utilities.responseHelper(
res,
Expand Down
8 changes: 6 additions & 2 deletions src/controllers/notificationController.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,19 +3,23 @@
import strings from '../utils/stringsUtil';
import responseUtil from '../utils/responseUtil';
import notifServices from '../services/notifServices';
import bookingNotifServices from '../services/bookingNotifServices';
import { userNotidicationQuery } from '../utils/db/queries/notificationQueries';

const { allNotif, oneNotif, updateNotif } = notifServices;
const { bookingAllNotif } = bookingNotifServices;
const { NOTIF_NOT_FOUND, NOTIF_FOUND } = strings.notifications;

export default class notificationController {

static async allNotifications(req, res) {
const notifications = await allNotif(userNotidicationQuery(req.user.payload.id));
if (!notifications.length) {
const bookingNotifications = await bookingAllNotif(req.user.payload.id);
const allNotifs = notifications.concat(bookingNotifications);
if (!allNotifs.length) {
return responseUtil(res, 404, NOTIF_NOT_FOUND);
}
return responseUtil(res, 200, NOTIF_FOUND, notifications);
return responseUtil(res, 200, NOTIF_FOUND, allNotifs);
}

static async oneNotification(req, res) {
Expand Down
2 changes: 1 addition & 1 deletion src/controllers/requestController.js
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,7 @@ export default class requestController {

request = request[1][0].dataValues;

await notifSender(subject, request, request.userId, APP_URL_BACKEND, activity);
await notifSender(subject, request, request.userId, APP_URL_BACKEND, activity, 'request');
return responseHelper(res, responseMessage, null, 200);
}
return responseHelper(res, strings.requests.NOT_FOUND, null, 404);
Expand Down
8 changes: 4 additions & 4 deletions src/database/migrations/20191023121501-create-booking.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,18 +18,18 @@ module.exports = {
type: Sequelize.INTEGER
},
checkIn: {
type: Sequelize.DATE
type: Sequelize.DATEONLY
},
checkOut: {
type: Sequelize.DATE
type: Sequelize.DATEONLY
},
createdAt: {
allowNull: false,
type: Sequelize.DATE
type: Sequelize.DATEONLY
},
updatedAt: {
allowNull: false,
type: Sequelize.DATE
type: Sequelize.DATEONLY
}
});
},
Expand Down
12 changes: 12 additions & 0 deletions src/database/migrations/20191105142130-addStatusIdBooking.js.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
module.exports = {
up: (queryInterface, Sequelize) => queryInterface.sequelize.transaction(t => Promise.all([
queryInterface.addColumn('bookings', 'statusId', {
type: Sequelize.INTEGER,
allowNull: false
}, { transaction: t }),
])),

down: queryInterface => queryInterface.sequelize.transaction(t => Promise.all([
queryInterface.removeColumn('bookings', 'statusId', { transaction: t }),
])),
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
'use strict';
module.exports = {
up: (queryInterface, Sequelize) => {
return queryInterface.createTable('bookingnotifications', {
id: {
allowNull: false,
autoIncrement: true,
primaryKey: true,
type: Sequelize.INTEGER
},
bookingId: {
type: Sequelize.INTEGER
},
userNotified: {
type: Sequelize.INTEGER
},
activity: {
type: Sequelize.STRING
},
isRead: {
type: Sequelize.BOOLEAN
},
createdAt: {
allowNull: false,
type: Sequelize.DATEONLY
},
updatedAt: {
allowNull: false,
type: Sequelize.DATEONLY
}
});
},
down: (queryInterface, Sequelize) => {
return queryInterface.dropTable('bookingnotifications');
}
};
13 changes: 11 additions & 2 deletions src/database/models/booking.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,22 @@ module.exports = (sequelize, DataTypes) => {
userId: DataTypes.INTEGER,
accommodationId: DataTypes.INTEGER,
bookedSpace: DataTypes.INTEGER,
checkIn: DataTypes.DATE,
checkOut: DataTypes.DATE
statusId: {
type: DataTypes.INTEGER,
defaultValue: 1,
},
checkIn: DataTypes.DATEONLY,
checkOut: DataTypes.DATEONLY
}, {});
booking.associate = function(models) {
booking.belongsTo(models.users, {
as'user',
      foreignKey'userId',
      targetKey'id',
});
booking.belongsTo(models.requestStatus, {
as'status',
      foreignKey'statusId',
      targetKey'id',
});
booking.belongsTo(models.accommodations, {
Expand Down
26 changes: 26 additions & 0 deletions src/database/models/bookingnotifications.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
'use strict';
module.exports = (sequelize, DataTypes) => {
const bookingnotifications = sequelize.define('bookingnotifications', {
bookingId: DataTypes.INTEGER,
userNotified: DataTypes.INTEGER,
activity: DataTypes.STRING,
isRead: {
type: DataTypes.BOOLEAN,
defaultValue: false,
},
}, {
tableName: 'bookingnotifications'
});
bookingnotifications.associate = function(models) {
// associations can be defined here
bookingnotifications.belongsTo(models.booking, {
as: 'booking',
foreignKey: 'bookingId',
});
bookingnotifications.belongsTo(models.users, {
as: 'notifiedUser',
foreignKey: 'userNotified'
});
};
return bookingnotifications;
};
12 changes: 12 additions & 0 deletions src/database/seeders/20191002153158-usersTableSeeder.js
Original file line number Diff line number Diff line change
Expand Up @@ -134,6 +134,18 @@ module.exports = {
createdAt: new Date(),
updatedAt: new Date()
},
{
username: 'anotherSupplier',
email: 'anothersupplier@caretbn.com',
password: '$2b$10$VyldWKIyiuVSqZYjmz4u8OepsFJFKzQipOQzhrhQKthgn8a9OI2Au',
isVerified: true,
emailNotif: true,
appNotif: true,
lineManager: 8,
role: 5,
createdAt: new Date(),
updatedAt: new Date()
},
])
]),

Expand Down
30 changes: 30 additions & 0 deletions src/database/seeders/20191016080543-accommodationsTableSeeder.js
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,36 @@ module.exports = {
createdAt: new Date(),
updatedAt: new Date()
},
{
name: 'Crocs Hotel',
description: 'Chop Chop',
locationId: 3,
availableSpace: '20',
cost: 150,
currency: 'USD',
highlights: 'Alligators',
amenities: 'Swim with Crocs',
owner: 7,
slug: 'crocs-hotel',
images: '"http://res.cloudinary.com/codeal/image/upload/v15712/19161/i2ro51fa8luaspg8j.png"',
createdAt: new Date(),
updatedAt: new Date()
},
{
name: 'Clouds',
description: 'Poua Poua',
locationId: 3,
availableSpace: '50',
cost: 75,
currency: 'USD',
highlights: 'Nuages',
amenities: 'Sky DIving',
owner: 11,
slug: 'crocs-hotel',
images: '"http://res.cloudinary.com/codeal/image/upload/v15712/19161/i2ro51fa8luaspg8j.png"',
createdAt: new Date(),
updatedAt: new Date()
},
])
]),

Expand Down
Loading

0 comments on commit 4874cb0

Please sign in to comment.