Discounts support / make rates configurable

This commit is contained in:
Senad Uka
2019-08-16 05:16:27 +02:00
parent d2ac43bac4
commit d788f66e1a
12 changed files with 370 additions and 24 deletions

View File

@@ -3,11 +3,13 @@
const moment = require('moment-timezone');
const { getAllIncidents } = require('./reports');
const { getActiveBookingsForMembersInDateRange } = require('./bookings');
const { getAllBookingsForMembersInDateRange } = require('./bookings');
const { DEFAULT_DATE_FORMAT, UI_TIMEZONE, incidentTypeExplanations, incidentType, unlockedIncidentLevelsPrices } = require('../../constants/constants');
const { getResourceMappings } = require('../officeRnD/resources');
const { fetchAllMembers } = require('../officeRnD/members');
const { fetchAllMembershipsAsMap } = require('../officeRnD/memberships');
const { discounts, DISCOUNT_PLANS } = require('../../constants/constants');
const createFeeFromIncident = (incident) => {
const {
@@ -222,17 +224,81 @@ const createFeeFromBooking = (booking, resourceMappings) => {
}
};
const createNegativeFeeForDiscount = (memberData, dateRange) => {
const { bookingData, member, membershipFees } = memberData;
const { totalBookedHours, totalChargedHours, totalBookingChargedFee } = bookingData;
const { memberId, officeId } = member;
let endDate = moment.utc().endOf('day').toISOString();
if (dateRange.endDate){
endDate = moment.utc(dateRange.endDate, DEFAULT_DATE_FORMAT).endOf('day').toISOString();
}
let membershipFeeForDiscount = 0;
membershipFees.forEach((membershipFee) => {
const {name, price} = membershipFee;
if (DISCOUNT_PLANS.indexOf(name) !== -1){
membershipFeeForDiscount = price;
}
});
const totalChargeFee = membershipFeeForDiscount + totalBookingChargedFee;
let discount = 0;
let discountPercentage = 0;
if (totalChargedHours >= discounts.LEVEL_2.hoursRequired){
discountPercentage = discounts.LEVEL_2.percentage;
const discountRate = discountPercentage / 100;
discount = totalChargeFee * discountRate;
}else if (totalChargedHours >= discounts.LEVEL_1.hoursRequired){
discountPercentage = discounts.LEVEL_1.percentage;
const discountRate = discountPercentage / 100;
discount = totalChargeFee * discountRate;
}else{
return null;
}
const formattedName = `[Discount] Total booked : ${totalBookedHours.toFixed(2)} hrs, Total charged : ${totalChargedHours.toFixed(2)} hrs, Discount : ${discountPercentage} %`;
return {
name: formattedName,
price: -discount.toFixed(2),
quantity: 1,
date: endDate,
member: memberId,
team: null,
office: officeId,
isPersonal: false,
}
};
const getMembersFeesForDateRange = (dateRange, memberIds) => {
return new Promise((resolve, reject) => {
const collectData = [getAllIncidents(dateRange, memberIds), getActiveBookingsForMembersInDateRange(dateRange, memberIds), getResourceMappings(), fetchAllMembers()];
const collectData = [getAllIncidents(dateRange, memberIds), getAllBookingsForMembersInDateRange(dateRange, memberIds), getResourceMappings(), fetchAllMembers(), fetchAllMembershipsAsMap()];
Promise.all(collectData)
.then((result) => {
const allIncidents = result[0];
const allActiveBookings = result[1];
const allBookings = result[1];
const resourceMappings = result[2];
const membersList = result[3];
const membershipsMap = result[4];
const membersMap = {};
const oneMemberObject = {
totalBookedHours: 0,
totalChargedHours: 0,
totalBookingChargedFee: 0,
};
membersList.forEach((member) => {
membersMap[member.memberId] = {
member,
bookingData: Object.assign({}, oneMemberObject),
membershipFees: membershipsMap[member.memberId],
};
});
const memberIdTeamMappings = {};
membersList.forEach((member) => {
@@ -241,14 +307,127 @@ const getMembersFeesForDateRange = (dateRange, memberIds) => {
const allFees = [];
allIncidents.forEach((incident) => allFees.push(createFeeFromIncident(incident)));
allActiveBookings.forEach((booking) => allFees.push(createFeeFromBooking(booking, resourceMappings)));
allIncidents.forEach((incident) => {
allFees.push(createFeeFromIncident(incident));
const incidentsValuableForDiscountCalculation = [
incidentType.UNSCHEDULED_INCIDENT_BEFORE_RESERVATION,
incidentType.UNSCHEDULED_INCIDENT_AFTER_RESERVATION,
incidentType.UNSCHEDULED_INCIDENT_STANDALONE,
incidentType.BOOKING_SHORTENED,
incidentType.BOOKING_CANCELED_LATE
];
const incidentTypeNumber = incident.incidentType;
if (incidentsValuableForDiscountCalculation.indexOf(incidentTypeNumber) === -1){
return;
}
const {
memberId,
oldBookingStartRaw,
oldBookingEndRaw,
newBookingStartRaw,
newBookingEndRaw,
unlockTimestampRaw,
lockTimestampRaw,
bookingStartRaw,
bookingEndRaw,
totalChargeFee
} = incident;
let chargedBookingLength = 0;
switch (incidentTypeNumber){
case incidentType.UNSCHEDULED_INCIDENT_BEFORE_RESERVATION:
const unlockMoment = moment.utc(unlockTimestampRaw);
const bookingStartMoment =moment.utc(bookingStartRaw);
if (unlockMoment.isValid() && bookingStartMoment.isValid()){
chargedBookingLength = bookingStartMoment.diff(unlockMoment, 'hours', true);
}
break;
case incidentType.UNSCHEDULED_INCIDENT_AFTER_RESERVATION:
const lockMoment = moment.utc(lockTimestampRaw);
const bookingEndMoment =moment.utc(bookingEndRaw);
if (lockMoment.isValid() && bookingEndMoment.isValid()){
chargedBookingLength = lockMoment.diff(bookingEndMoment, 'hours', true);
}
break;
case incidentType.UNSCHEDULED_INCIDENT_STANDALONE:
const unlockMomentStandalone = moment.utc(unlockTimestampRaw);
const lockMomentStandalone = moment.utc(lockTimestampRaw);
if (unlockMomentStandalone.isValid() && lockMomentStandalone.isValid()){
chargedBookingLength = lockMomentStandalone.diff(unlockMomentStandalone, 'hours', true);
}
break;
case incidentType.BOOKING_SHORTENED:
const oldBookingStartMoment = moment.utc(oldBookingStartRaw);
const oldBookingEndMoment = moment.utc(oldBookingEndRaw);
const newBookingStartMoment = moment.utc(newBookingStartRaw);
const newBookingEndMoment = moment.utc(newBookingEndRaw);
if (oldBookingStartMoment.isValid() && oldBookingEndMoment.isValid() && newBookingStartMoment.isValid() && newBookingEndMoment.isValid()){
const oldBookingLength = oldBookingEndMoment.diff(oldBookingStartMoment, 'hours', true);
const newBookingLength = newBookingEndMoment.diff(newBookingStartMoment, 'hours', true);
chargedBookingLength = Math.abs(oldBookingLength - newBookingLength);
}
break;
case incidentType.BOOKING_CANCELED_LATE:
const startMoment = moment.utc(oldBookingStartRaw);
const endMoment = moment.utc(oldBookingEndRaw);
if (startMoment.isValid() && endMoment.isValid()) {
chargedBookingLength = endMoment.diff(startMoment, 'hours', true);
// membersMap[memberId].bookingData.totalBookedHours += bookingLength;
// "booked hours" is counted in canceled booking section
}
break;
}
membersMap[memberId].bookingData.totalChargedHours += chargedBookingLength;
membersMap[memberId].bookingData.totalBookingChargedFee += totalChargeFee;
});
allBookings.forEach((booking) => {
const {memberId, start, end, timezone, hourlyRate, canceled } = booking.get();
const startMoment = moment.tz(start, timezone);
const endMoment = moment.tz(end, timezone);
if (startMoment.isValid() && endMoment.isValid()) {
const bookingLength = endMoment.diff(startMoment, 'hours', true);
if (!membersMap[memberId] || !membersMap[memberId].bookingData) {
membersMap[memberId].bookingData = Object.assign({}, oneMemberObject);
}
membersMap[memberId].bookingData.totalBookedHours += bookingLength;
if (!canceled){
membersMap[memberId].bookingData.totalChargedHours += bookingLength;
const bookingFee = bookingLength * hourlyRate;
membersMap[memberId].bookingData.totalBookingChargedFee += bookingFee;
allFees.push(createFeeFromBooking(booking, resourceMappings));
}
}
});
//add discount
memberIds.forEach((memberId) => {
const discountFee = createNegativeFeeForDiscount(membersMap[memberId], dateRange);
if (discountFee){
allFees.push(discountFee);
}
});
allFees.forEach((fee) => {
fee.team = memberIdTeamMappings[fee.member] || null;
});
resolve(allFees);
})
.catch((error) => {
console.log(error);