Files
old-psihologija/services/integration/invoiceIntegration.js

442 lines
20 KiB
JavaScript
Raw Normal View History

2019-07-25 02:00:27 +02:00
'use strict';
const moment = require('moment-timezone');
const { getAllIncidents } = require('./reports');
2019-08-13 15:36:14 +02:00
const { getAllBookingsForMembersInDateRange } = require('./bookings');
2019-07-25 02:00:27 +02:00
const { DEFAULT_DATE_FORMAT, UI_TIMEZONE, incidentTypeExplanations, incidentType, unlockedIncidentLevelsPrices } = require('../../constants/constants');
const { getResourceMappings } = require('../officeRnD/resources');
const { fetchAllMembers } = require('../officeRnD/members');
2019-08-13 15:36:14 +02:00
const { fetchAllMembershipsAsMap } = require('../officeRnD/memberships');
2019-08-14 12:02:04 +02:00
const { discounts, DISCOUNT_PLANS } = require('../../constants/constants');
2019-07-25 02:00:27 +02:00
const createFeeFromIncident = (incident) => {
const {
memberId,
officeId,
officeName,
resourceName,
oldResourceName,
newResourceName,
bookingStartRaw,
bookingEndRaw,
oldBookingStartRaw,
oldBookingEndRaw,
newBookingStartRaw,
newBookingEndRaw,
unlockTimestampRaw,
lockTimestampRaw,
incidentLevel,
timeIntervalsToCharge,
incidentPrice,
chargePrice,
totalChargeFee,
incidentTimestampRaw
} = incident;
const incidentTypeNumber = incident.incidentType;
const incidentExplanation = incidentTypeExplanations[incidentTypeNumber];
2019-07-25 02:36:38 +02:00
let date = '';
2019-07-25 02:00:27 +02:00
let price = 0;
let quantity = 0;
2019-07-31 13:55:02 +02:00
// let priceExplanation = '';
2019-07-25 02:00:27 +02:00
let bookingTimeExplanation = '';
let incidentTimeExplanation = '';
2019-07-31 13:55:02 +02:00
let incidentTypeExplanation = '';
2019-07-25 02:00:27 +02:00
let additionalIncidentExplanation = '';
let roomExplanation = '';
let dateExplanation = '';
const bookingStartMoment = moment.tz(bookingStartRaw, UI_TIMEZONE);
const bookingEndMoment = moment.tz(bookingEndRaw, UI_TIMEZONE);
const unlockMoment = moment.tz(unlockTimestampRaw, UI_TIMEZONE);
const lockMoment = moment.tz(lockTimestampRaw, UI_TIMEZONE);
const oldBookingStartMoment = moment.tz(oldBookingStartRaw, UI_TIMEZONE);
const oldBookingEndMoment = moment.tz(oldBookingEndRaw, UI_TIMEZONE);
const newBookingStartMoment = moment.tz(newBookingStartRaw, UI_TIMEZONE);
const newBookingEndMoment = moment.tz(newBookingEndRaw, UI_TIMEZONE);
const incidentTimestampMoment = moment.tz(incidentTimestampRaw, UI_TIMEZONE);
switch (incidentTypeNumber){
case incidentType.UNLOCKED_INCIDENT_RELATED_WITH_RESERVATION:
roomExplanation = resourceName;
2019-07-31 13:55:02 +02:00
dateExplanation = bookingStartMoment.clone().startOf('day').format('MMM DD');
2019-07-25 02:00:27 +02:00
bookingTimeExplanation = `${bookingStartMoment.clone().format('HH:mm a')} - ${bookingEndMoment.clone().format('HH:mm a')}`;
2019-07-31 13:55:02 +02:00
incidentTimeExplanation = ''; // `UNLOCK : ${unlockMoment.clone().format('HH:mm a')}`;
additionalIncidentExplanation = ` ${unlockedIncidentLevelsPrices[incidentLevel].description},`;
incidentTypeExplanation = '';
2019-07-25 02:00:27 +02:00
2019-07-25 02:36:38 +02:00
date = bookingStartMoment.clone().startOf('day').format();
price = +incidentPrice.toFixed(2);
2019-07-31 13:55:02 +02:00
quantity = 1.00;
// priceExplanation = `$${price}, 1 x $${price.toFixed(2)}`;
2019-07-25 02:00:27 +02:00
break;
case incidentType.UNSCHEDULED_INCIDENT_BEFORE_RESERVATION:
roomExplanation = resourceName;
2019-07-31 13:55:02 +02:00
dateExplanation = bookingStartMoment.clone().startOf('day').format('MMM DD');
2019-07-25 02:00:27 +02:00
bookingTimeExplanation = `${bookingStartMoment.clone().format('HH:mm a')} - ${bookingEndMoment.clone().format('HH:mm a')}`;
2019-07-31 13:55:02 +02:00
incidentTimeExplanation = ` Unlock : ${unlockMoment.clone().format('HH:mm a')},`;
incidentTypeExplanation = '';
2019-07-25 02:00:27 +02:00
2019-07-25 02:36:38 +02:00
date = bookingStartMoment.clone().startOf('day').format();
price = +chargePrice.toFixed(2);
quantity = +timeIntervalsToCharge.toFixed(2);
2019-07-31 13:55:02 +02:00
// priceExplanation = `$${totalChargeFee.toFixed(2)}, ${quantity} x $${price.toFixed(2)}`;
2019-07-25 02:00:27 +02:00
break;
case incidentType.UNSCHEDULED_INCIDENT_AFTER_RESERVATION:
roomExplanation = resourceName;
2019-07-31 13:55:02 +02:00
dateExplanation = bookingStartMoment.clone().startOf('day').format('MMM DD');
2019-07-25 02:00:27 +02:00
bookingTimeExplanation = `${bookingStartMoment.clone().format('HH:mm a')} - ${bookingEndMoment.clone().format('HH:mm a')}`;
2019-07-31 13:55:02 +02:00
incidentTimeExplanation = ` Lock : ${lockMoment.clone().format('HH:mm a')},`;
incidentTypeExplanation = '';
2019-07-25 02:00:27 +02:00
2019-07-25 02:36:38 +02:00
date = bookingStartMoment.clone().startOf('day').format();
price = +chargePrice.toFixed(2);
quantity = +timeIntervalsToCharge.toFixed(2);
2019-07-31 13:55:02 +02:00
// priceExplanation = `$${totalChargeFee.toFixed(2)}, ${quantity} x $${price.toFixed(2)}`;
2019-07-25 02:00:27 +02:00
break;
case incidentType.UNLOCKED_INCIDENT_STANDALONE:
roomExplanation = resourceName;
2019-07-31 13:55:02 +02:00
dateExplanation = unlockMoment.clone().startOf('day').format('MMM DD');
bookingTimeExplanation = `No reservation`;
incidentTimeExplanation = ''; // `UNLOCK : ${unlockMoment.clone().format('HH:mm a')}`;
additionalIncidentExplanation = ` ${unlockedIncidentLevelsPrices[incidentLevel].description},`;
incidentTypeExplanation = '';
2019-07-25 02:00:27 +02:00
2019-07-25 02:36:38 +02:00
date = unlockMoment.clone().startOf('day').format();
price = +incidentPrice.toFixed(2);
2019-07-31 13:55:02 +02:00
quantity = 1.00;
// priceExplanation = `$${price.toFixed(2)}, 1 x $${price.toFixed(2)}`;
2019-07-25 02:00:27 +02:00
break;
case incidentType.UNSCHEDULED_INCIDENT_STANDALONE:
roomExplanation = resourceName;
2019-07-31 13:55:02 +02:00
dateExplanation = unlockMoment.clone().startOf('day').format('MMM DD');
bookingTimeExplanation = `No reservation`;
incidentTimeExplanation = ` Unlock : ${unlockMoment.clone().format('HH:mm a')} Lock : ${lockMoment.clone().format('HH:mm a')},`;
incidentTypeExplanation = '';
2019-07-25 02:00:27 +02:00
2019-07-25 02:36:38 +02:00
date = unlockMoment.clone().startOf('day').format();
price = +chargePrice.toFixed(2);
quantity = +timeIntervalsToCharge.toFixed(2);
2019-07-31 13:55:02 +02:00
// priceExplanation = `$${totalChargeFee.toFixed(2)}, ${quantity} x $${price.toFixed(2)}`;
2019-07-25 02:00:27 +02:00
break;
case incidentType.BOOKING_MOVED_TO_ANOTHER_DAY:
if (oldResourceName !== newResourceName){
roomExplanation = `${oldResourceName} -> ${newResourceName}`;
}else{
roomExplanation = oldResourceName;
}
2019-07-31 13:55:02 +02:00
dateExplanation = `${oldBookingStartMoment.clone().format('MMM DD')} -> ${newBookingStartMoment.clone().format('MMM DD')}`;
2019-07-25 02:00:27 +02:00
bookingTimeExplanation = `(${oldBookingStartMoment.clone().format('HH:mm a')} - ${oldBookingEndMoment.clone().format('HH:mm a')}) -> (${newBookingStartMoment.clone().format('HH:mm a')} - ${newBookingEndMoment.clone().format('HH:mm a')})`;
2019-07-31 13:55:02 +02:00
incidentTimeExplanation = ` Moved on : ${incidentTimestampMoment.clone().format('MMM DD, HH:mm a')},`;
incidentTypeExplanation = '[Cancellation]';
2019-07-25 02:36:38 +02:00
date = incidentTimestampMoment.clone().startOf('day').format();
2019-07-25 02:00:27 +02:00
price = +totalChargeFee.toFixed(2);
2019-07-31 13:55:02 +02:00
quantity = 1.00;
// priceExplanation = `$${totalChargeFee.toFixed(2)}, 1 x $${price.toFixed(2)}`;
2019-07-25 02:00:27 +02:00
break;
case incidentType.BOOKING_SHORTENED:
if (oldResourceName !== newResourceName){
roomExplanation = `${oldResourceName} -> ${newResourceName}`;
}else{
roomExplanation = oldResourceName;
}
2019-07-31 13:55:02 +02:00
dateExplanation = `${oldBookingStartMoment.clone().format('MMM DD')}`;
2019-07-25 02:00:27 +02:00
bookingTimeExplanation = `(${oldBookingStartMoment.clone().format('HH:mm a')} - ${oldBookingEndMoment.clone().format('HH:mm a')}) -> (${newBookingStartMoment.clone().format('HH:mm a')} - ${newBookingEndMoment.clone().format('HH:mm a')})`;
2019-07-31 13:55:02 +02:00
incidentTimeExplanation = ` Shortened on : ${incidentTimestampMoment.clone().format('MMM DD, HH:mm a')},`;
incidentTypeExplanation = '[Cancellation]';
2019-07-25 02:36:38 +02:00
date = incidentTimestampMoment.clone().startOf('day').format();
2019-07-25 02:00:27 +02:00
price = +totalChargeFee.toFixed(2);
2019-07-31 13:55:02 +02:00
quantity = 1.00;
// priceExplanation = `$${totalChargeFee.toFixed(2)}, 1 x $${price.toFixed(2)}`;
2019-07-25 02:00:27 +02:00
break;
case incidentType.BOOKING_CANCELED_LATE:
roomExplanation = oldResourceName;
2019-07-31 13:55:02 +02:00
dateExplanation = `${oldBookingStartMoment.clone().format('MMM DD')}`;
2019-07-25 02:00:27 +02:00
bookingTimeExplanation = `${oldBookingStartMoment.clone().format('HH:mm a')} - ${oldBookingEndMoment.clone().format('HH:mm a')}`;
2019-07-31 13:55:02 +02:00
incidentTimeExplanation = ` Canceled on : ${incidentTimestampMoment.clone().format('MMM DD, HH:mm a')},`;
incidentTypeExplanation = '[Cancellation]';
2019-07-25 02:36:38 +02:00
date = incidentTimestampMoment.clone().startOf('day').format();
2019-07-25 02:00:27 +02:00
price = +totalChargeFee.toFixed(2);
2019-07-31 13:55:02 +02:00
quantity = 1.00;
// priceExplanation = `$${totalChargeFee.toFixed(2)}, 1 x $${price.toFixed(2)}`;
2019-07-25 02:00:27 +02:00
break;
}
2019-07-31 13:55:02 +02:00
const formattedName = `${incidentTypeExplanation} ${incidentExplanation},${additionalIncidentExplanation}${incidentTimeExplanation} ${officeName}, ${roomExplanation}, ${dateExplanation}, ${bookingTimeExplanation}`;
2019-07-25 02:00:27 +02:00
return {
name: formattedName,
price,
quantity,
2019-07-25 02:36:38 +02:00
date,
2019-07-25 02:00:27 +02:00
member: memberId,
team: null,
office: officeId,
isPersonal: false,
}
};
const createFeeFromBooking = (booking, resourceMappings) => {
const { officeId, resourceId, memberId, start, end, timezone, hourlyRate } = booking;
const { officesMap, resourcesMap } = resourceMappings;
const startMoment = moment.tz(start, DEFAULT_DATE_FORMAT, timezone);
const endMoment = moment.tz(end, DEFAULT_DATE_FORMAT, timezone);
const reservationLength = endMoment.diff(startMoment, 'hours', true);
const officeName = officesMap[officeId].officeName || 'Unknown';
const resourceName = resourcesMap[resourceId].resourceName || 'Unknown';
2019-07-31 13:55:02 +02:00
const formattedDate = startMoment.clone().startOf('day').format('MMM DD');
2019-07-25 02:00:27 +02:00
const formattedStartTime = startMoment.format('HH:mm a');
const formattedEndTime = endMoment.format('HH:mm a');
const formattedName = `${officeName}, ${resourceName}, ${formattedDate} [${formattedStartTime} - ${formattedEndTime}]`;
2019-07-25 02:00:27 +02:00
return {
name: formattedName,
price: +hourlyRate.toFixed(2),
quantity: +reservationLength.toFixed(2),
2019-07-25 02:00:27 +02:00
date: startMoment.startOf('day').toISOString(),
member: memberId,
team: null,
office: officeId,
isPersonal: false,
}
};
2019-08-14 08:35:04 +02:00
const createNegativeFeeForDiscount = (memberData, dateRange) => {
2019-08-14 12:02:04 +02:00
const { bookingData, member, membershipFees } = memberData;
2019-08-13 15:36:14 +02:00
const { totalBookedHours, totalChargedHours, totalBookingChargedFee } = bookingData;
const { memberId, officeId } = member;
2019-08-14 08:35:04 +02:00
let endDate = moment.utc().endOf('day').toISOString();
if (dateRange.endDate){
endDate = moment.utc(dateRange.endDate, DEFAULT_DATE_FORMAT).endOf('day').toISOString();
}
2019-08-14 12:02:04 +02:00
let membershipFeeForDiscount = 0;
membershipFees.forEach((membershipFee) => {
const {name, price} = membershipFee;
if (DISCOUNT_PLANS.indexOf(name) !== -1){
membershipFeeForDiscount = price;
}
});
const totalChargeFee = membershipFeeForDiscount + totalBookingChargedFee;
2019-08-13 15:36:14 +02:00
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{
2019-08-14 12:02:04 +02:00
return null;
2019-08-13 15:36:14 +02:00
}
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,
2019-08-14 08:35:04 +02:00
date: endDate,
2019-08-13 15:36:14 +02:00
member: memberId,
team: null,
office: officeId,
isPersonal: false,
}
};
2019-07-25 02:00:27 +02:00
const getMembersFeesForDateRange = (dateRange, memberIds) => {
return new Promise((resolve, reject) => {
2019-08-13 15:36:14 +02:00
const collectData = [getAllIncidents(dateRange, memberIds), getAllBookingsForMembersInDateRange(dateRange, memberIds), getResourceMappings(), fetchAllMembers(), fetchAllMembershipsAsMap()];
2019-07-25 02:00:27 +02:00
Promise.all(collectData)
.then((result) => {
const allIncidents = result[0];
2019-08-13 15:36:14 +02:00
const allBookings = result[1];
2019-07-25 02:00:27 +02:00
const resourceMappings = result[2];
const membersList = result[3];
2019-08-13 15:36:14 +02:00
const membershipsMap = result[4];
const membersMap = {};
const oneMemberObject = {
totalBookedHours: 0,
totalChargedHours: 0,
totalBookingChargedFee: 0,
};
2019-07-25 02:00:27 +02:00
membersList.forEach((member) => {
2019-08-13 15:36:14 +02:00
membersMap[member.memberId] = {
member,
bookingData: Object.assign({}, oneMemberObject),
2019-08-14 12:02:04 +02:00
membershipFees: membershipsMap[member.memberId],
2019-08-13 15:36:14 +02:00
};
2019-07-25 02:00:27 +02:00
});
const memberIdTeamMappings = {};
membersList.forEach((member) => {
memberIdTeamMappings[member.memberId] = member.teamId;
});
const allFees = [];
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;
}
2019-08-13 15:36:14 +02:00
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;
});
2019-08-13 15:36:14 +02:00
allBookings.forEach((booking) => {
const {memberId, start, end, timezone, hourlyRate, canceled } = booking.get();
2019-08-13 15:36:14 +02:00
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);
}
2019-07-25 02:00:27 +02:00
2019-08-13 15:36:14 +02:00
membersMap[memberId].bookingData.totalBookedHours += bookingLength;
2019-07-25 02:00:27 +02:00
if (!canceled){
2019-08-13 15:36:14 +02:00
membersMap[memberId].bookingData.totalChargedHours += bookingLength;
const bookingFee = bookingLength * hourlyRate;
membersMap[memberId].bookingData.totalBookingChargedFee += bookingFee;
allFees.push(createFeeFromBooking(booking, resourceMappings));
2019-08-13 15:36:14 +02:00
}
}
2019-07-25 02:00:27 +02:00
});
//add discount
memberIds.forEach((memberId) => {
const discountFee = createNegativeFeeForDiscount(membersMap[memberId], dateRange);
if (discountFee){
allFees.push(discountFee);
}
});
2019-08-13 15:36:14 +02:00
allFees.forEach((fee) => {
fee.team = memberIdTeamMappings[fee.member] || null;
});
2019-08-13 15:36:14 +02:00
resolve(allFees);
2019-08-13 15:36:14 +02:00
2019-07-25 02:00:27 +02:00
})
.catch((error) => {
console.log(error);
reject(error);
});
});
};
module.exports = {
getMembersFeesForDateRange,
};