'use strict'; const moment = require('moment-timezone'); const { getAllIncidents } = require('./reports'); 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 { getChargedCanceledReservations } = require('../integration/bookingChangeCharges'); const { discounts, DISCOUNT_PLANS } = require('../../constants/constants'); 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]; let date = ''; let price = 0; let quantity = 0; // let priceExplanation = ''; let bookingTimeExplanation = ''; let incidentTimeExplanation = ''; let incidentTypeExplanation = ''; 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; dateExplanation = bookingStartMoment.clone().startOf('day').format('MMM DD'); bookingTimeExplanation = `${bookingStartMoment.clone().format('HH:mm a')} - ${bookingEndMoment.clone().format('HH:mm a')}`; incidentTimeExplanation = ''; // `UNLOCK : ${unlockMoment.clone().format('HH:mm a')}`; additionalIncidentExplanation = ` ${unlockedIncidentLevelsPrices[incidentLevel].description},`; incidentTypeExplanation = ''; date = bookingStartMoment.clone().startOf('day').format(); price = +incidentPrice.toFixed(2); quantity = 1.00; // priceExplanation = `$${price}, 1 x $${price.toFixed(2)}`; break; case incidentType.UNSCHEDULED_INCIDENT_BEFORE_RESERVATION: roomExplanation = resourceName; dateExplanation = bookingStartMoment.clone().startOf('day').format('MMM DD'); bookingTimeExplanation = `${bookingStartMoment.clone().format('HH:mm a')} - ${bookingEndMoment.clone().format('HH:mm a')}`; incidentTimeExplanation = ` Unlock : ${unlockMoment.clone().format('HH:mm a')},`; incidentTypeExplanation = ''; date = bookingStartMoment.clone().startOf('day').format(); price = +chargePrice.toFixed(2); quantity = +timeIntervalsToCharge.toFixed(2); // priceExplanation = `$${totalChargeFee.toFixed(2)}, ${quantity} x $${price.toFixed(2)}`; break; case incidentType.UNSCHEDULED_INCIDENT_AFTER_RESERVATION: roomExplanation = resourceName; dateExplanation = bookingStartMoment.clone().startOf('day').format('MMM DD'); bookingTimeExplanation = `${bookingStartMoment.clone().format('HH:mm a')} - ${bookingEndMoment.clone().format('HH:mm a')}`; incidentTimeExplanation = ` Lock : ${lockMoment.clone().format('HH:mm a')},`; incidentTypeExplanation = ''; date = bookingStartMoment.clone().startOf('day').format(); price = +chargePrice.toFixed(2); quantity = +timeIntervalsToCharge.toFixed(2); // priceExplanation = `$${totalChargeFee.toFixed(2)}, ${quantity} x $${price.toFixed(2)}`; break; case incidentType.UNLOCKED_INCIDENT_STANDALONE: roomExplanation = resourceName; dateExplanation = unlockMoment.clone().startOf('day').format('MMM DD'); bookingTimeExplanation = `No reservation`; incidentTimeExplanation = ''; // `UNLOCK : ${unlockMoment.clone().format('HH:mm a')}`; additionalIncidentExplanation = ` ${unlockedIncidentLevelsPrices[incidentLevel].description},`; incidentTypeExplanation = ''; date = unlockMoment.clone().startOf('day').format(); price = +incidentPrice.toFixed(2); quantity = 1.00; // priceExplanation = `$${price.toFixed(2)}, 1 x $${price.toFixed(2)}`; break; case incidentType.UNSCHEDULED_INCIDENT_STANDALONE: roomExplanation = resourceName; 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 = ''; date = unlockMoment.clone().startOf('day').format(); price = +chargePrice.toFixed(2); quantity = +timeIntervalsToCharge.toFixed(2); // priceExplanation = `$${totalChargeFee.toFixed(2)}, ${quantity} x $${price.toFixed(2)}`; break; case incidentType.BOOKING_MOVED_TO_ANOTHER_DAY: if (oldResourceName !== newResourceName){ roomExplanation = `${oldResourceName} -> ${newResourceName}`; }else{ roomExplanation = oldResourceName; } dateExplanation = `${oldBookingStartMoment.clone().format('MMM DD')} -> ${newBookingStartMoment.clone().format('MMM DD')}`; 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')})`; incidentTimeExplanation = ` Moved on : ${incidentTimestampMoment.clone().format('MMM DD, HH:mm a')},`; incidentTypeExplanation = '[Cancellation]'; date = incidentTimestampMoment.clone().startOf('day').format(); price = +totalChargeFee.toFixed(2); quantity = 1.00; // priceExplanation = `$${totalChargeFee.toFixed(2)}, 1 x $${price.toFixed(2)}`; break; case incidentType.BOOKING_SHORTENED: if (oldResourceName !== newResourceName){ roomExplanation = `${oldResourceName} -> ${newResourceName}`; }else{ roomExplanation = oldResourceName; } dateExplanation = `${oldBookingStartMoment.clone().format('MMM DD')}`; 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')})`; incidentTimeExplanation = ` Shortened on : ${incidentTimestampMoment.clone().format('MMM DD, HH:mm a')},`; incidentTypeExplanation = '[Cancellation]'; date = incidentTimestampMoment.clone().startOf('day').format(); price = +totalChargeFee.toFixed(2); quantity = 1.00; // priceExplanation = `$${totalChargeFee.toFixed(2)}, 1 x $${price.toFixed(2)}`; break; case incidentType.BOOKING_CANCELED_LATE: roomExplanation = oldResourceName; dateExplanation = `${oldBookingStartMoment.clone().format('MMM DD')}`; bookingTimeExplanation = `${oldBookingStartMoment.clone().format('HH:mm a')} - ${oldBookingEndMoment.clone().format('HH:mm a')}`; incidentTimeExplanation = ` Canceled on : ${incidentTimestampMoment.clone().format('MMM DD, HH:mm a')},`; incidentTypeExplanation = '[Cancellation]'; date = incidentTimestampMoment.clone().startOf('day').format(); price = +totalChargeFee.toFixed(2); quantity = 1.00; // priceExplanation = `$${totalChargeFee.toFixed(2)}, 1 x $${price.toFixed(2)}`; break; } const formattedName = `${incidentTypeExplanation} ${incidentExplanation},${additionalIncidentExplanation}${incidentTimeExplanation} ${officeName}, ${roomExplanation}, ${dateExplanation}, ${bookingTimeExplanation}`; return { name: formattedName, price, quantity, date, 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'; const formattedDate = startMoment.clone().startOf('day').format('MMM DD'); const formattedStartTime = startMoment.format('HH:mm a'); const formattedEndTime = endMoment.format('HH:mm a'); const formattedName = `${officeName}, ${resourceName}, ${formattedDate} [${formattedStartTime} - ${formattedEndTime}]`; return { name: formattedName, price: +hourlyRate.toFixed(2), quantity: +reservationLength.toFixed(2), date: startMoment.startOf('day').toISOString(), member: memberId, team: null, office: officeId, isPersonal: false, } }; 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), getAllBookingsForMembersInDateRange(dateRange, memberIds), getResourceMappings(), fetchAllMembers(), fetchAllMembershipsAsMap()]; Promise.all(collectData) .then((result) => { const allIncidents = result[0]; 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 reservationIdsForAdditionalData = []; const allActiveBookings = []; allBookings.forEach((booking) => { const {reservationId, memberId, start, end, timezone, canceled, hourlyRate} = 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) { reservationIdsForAdditionalData.push(reservationId); } else { allActiveBookings.push(booking); membersMap[memberId].bookingData.totalChargedHours += bookingLength; const bookingFee = bookingLength * hourlyRate; membersMap[memberId].bookingData.totalBookingChargedFee += bookingFee; } } }); getChargedCanceledReservations(reservationIdsForAdditionalData) .then((incidents) => { incidents.forEach((incident) => { const {memberId, oldBookingStart, oldBookingEnd, chargeFee} = incident.get(); const startMoment = moment.tz(oldBookingStart, UI_TIMEZONE); const endMoment = moment.tz(oldBookingEnd, UI_TIMEZONE); if (startMoment.isValid() && endMoment.isValid()) { const bookingLength = endMoment.diff(startMoment, 'hours', true); membersMap[memberId].bookingData.totalChargedHours += bookingLength; membersMap[memberId].bookingData.totalBookingChargedFee += chargeFee; } }); const memberIdTeamMappings = {}; membersList.forEach((member) => { memberIdTeamMappings[member.memberId] = member.teamId; }); const allFees = []; allIncidents.forEach((incident) => allFees.push(createFeeFromIncident(incident))); allActiveBookings.forEach((booking) => allFees.push(createFeeFromBooking(booking, resourceMappings))); //add discount memberIds.forEach((memberId) => { allFees.push(createNegativeFeeForDiscount(membersMap[memberId], dateRange)); }); allFees.forEach((fee) => { fee.team = memberIdTeamMappings[fee.member] || null; }); resolve(allFees); }) .catch((error) => reject(error)); }) .catch((error) => { console.log(error); reject(error); }); }); }; module.exports = { getMembersFeesForDateRange, };