import moment from 'moment' import GeneralUtils from '../../utils/general' import * as SendBirdController from '../../controllers/sendbird' import { ProjectGetPayload } from 'nexus-plugin-prisma/client' import { prismaClient as prisma } from '../../prismaClient' const PROJECT_ANALYTIC_FIELDS = ` id name numberOfProperties building { id properties { id address { apartmentNumber } } } users { id hasInstalledApp hasReceivedInvitation property { id } properties { id } } ` const getManagingCompaniesDataHelper = async (managingCompanyIds: string[]) => { const managingCompanies = await prisma.managingCompany.findMany({ where: { AND: [ { ...(managingCompanyIds && managingCompanyIds.length ? [{ id: { in: managingCompanyIds } }] : []), id: { notIn: ['cjp9ibdcp001p0914q2n4sau9', 'cjue5h70b01kn0803m3yo24fq'], }, }, ], }, include: { projects: true, }, }) return Promise.resolve(managingCompanies) } type ProjectWithRelations = ProjectGetPayload<{ select: { id: true name: true numberOfProperties: true managingCompany: { select: { longName: true } } building: { select: { properties: { select: { id: true address: { select: { apartmentNumber: true } } } } } } users: { select: { id: true hasInstalledApp: true hasReceivedInvitation: true property: { select: { id: true } } properties: { select: { id: true } } } } } }> const getPropertiesAndResidentsStatsForProject = async (project) => { const projectWithGoodFields: Partial = await prisma.project.findOne({ where: { id: project.id, }, select: { id: true, name: true, numberOfProperties: true, managingCompany: { select: { longName: true, }, }, building: { select: { properties: { select: { id: true, address: { select: { apartmentNumber: true, }, }, }, }, }, }, users: { select: { id: true, hasInstalledApp: true, hasReceivedInvitation: true, property: { select: { id: true, }, }, properties: { select: { id: true, }, }, }, }, }, }) const usersThatInstalledTheApp = projectWithGoodFields?.users.filter((u) => u.hasInstalledApp) const usersThatReceivedInvitation = projectWithGoodFields?.users.filter( (u) => u.hasReceivedInvitation ) // Properties const numberOfProperties = GeneralUtils.uniq( projectWithGoodFields.building.properties, 'address.apartmentNumber' ).length const numberOfPropertiesThatReceivedAtLeastOneInvitation = projectWithGoodFields?.building.properties.filter( (property) => usersThatReceivedInvitation.some( (user) => (user.property && user.property.id === property.id) || user.properties.some((p) => p.id === property.id) ) ).length const numberOfInstalledProperties = projectWithGoodFields.building.properties.filter((property) => usersThatInstalledTheApp.some( (user) => (user.property && user.property.id === property.id) || user.properties.some((p) => p.id === property.id) ) ).length const percentageInstalledProperties = Math.floor( (numberOfInstalledProperties / numberOfPropertiesThatReceivedAtLeastOneInvitation) * 100 ).toFixed(2) // Residents const numberOfResidents = projectWithGoodFields.users.length const numberOfInstalledResidents = usersThatInstalledTheApp.length const numberOfResidentsThatReceivedInvitation = usersThatReceivedInvitation.length const percentageInstalledResidents = Math.floor( (numberOfInstalledResidents / numberOfResidentsThatReceivedInvitation) * 100 ).toFixed(2) return Promise.resolve({ numberOfResidents, numberOfResidentsThatReceivedInvitation, numberOfInstalledResidents, percentageInstalledResidents, numberOfProperties, numberOfPropertiesThatReceivedAtLeastOneInvitation, numberOfInstalledProperties, percentageInstalledProperties, }) } const getManagingCompaniesData = async (req, res) => { const { managingCompanyId } = req.params try { const data = await getManagingCompaniesDataHelper(managingCompanyId ? [managingCompanyId] : []) return res.status(200).json(data) } catch (error) { console.log(error) res.status(500).json({ error: { message: error.message } }) } } const getGlobalData = async (req, res) => { const [ totalNumberOfResidents, totalNumberOfInvitedResidents, totalNumberOfInstalledResidents, totalNumberOfProperties, totalNumberOfInvitedProperties, totalNumberOfInstalledProperties, ] = await Promise.all([ prisma.user.count({ where: { role: 'RESIDENT', active: true } }), prisma.user.count({ where: { role: 'RESIDENT', active: true, OR: [{ hasReceivedInvitation: true }, { hasInstalledApp: true }], }, }), prisma.user.count({ where: { role: 'RESIDENT', active: true, hasInstalledApp: true } }), prisma.property.count({ where: {} }), prisma.property.count({ where: { OR: [ { OR: [ { users: { some: { hasReceivedInvitation: true, }, }, }, { users: { some: { hasInstalledApp: true, }, }, }, { owners: { some: { hasReceivedInvitation: true, }, }, }, { owners: { some: { hasInstalledApp: true, }, }, }, ], }, ], }, }), prisma.property.count({ where: { OR: [ { users: { some: { hasInstalledApp: true, }, }, owners: { some: { hasInstalledApp: true, }, }, }, ], }, }), ]) return res.status(200).json({ totalNumberOfResidents, totalNumberOfInvitedResidents, totalNumberOfInstalledResidents, totalNumberOfProperties, totalNumberOfInvitedProperties, totalNumberOfInstalledProperties, }) } async function getManagingCompanyDetailedData(req, res) { const { managingCompanyId } = req.params const managingCompanies = await prisma.managingCompany.findMany({ where: { AND: [ ...(managingCompanyId ? [{ id: managingCompanyId }] : []), { id: { notIn: ['cjp9ibdcp001p0914q2n4sau9', 'cjue5h70b01kn0803m3yo24fq'], }, }, ], }, select: { id: true, shortName: true, longName: true, projects: { select: { id: true, colorHex: true, name: true, launchDate: true, lastDataSyncDate: true, lastCondoManagerDataPushDate: true, numberOfProperties: true, logo: { select: { url: true, }, }, posts: { select: { createdAt: true, id: true, }, }, events: { select: { createdAt: true, id: true, }, }, contacts: { select: { id: true, }, }, folders: { select: { files: { select: { createdAt: true, id: true, }, }, }, }, tasks: { select: { createdAt: true, id: true, }, }, reservations: { select: { createdAt: true, id: true, }, }, amenities: { select: { createdAt: true, id: true, }, }, packages: { select: { createdAt: true, id: true, }, }, rules: { select: { id: true, }, }, boardMembers: { select: { id: true, }, }, ads: { select: { id: true, }, }, users: { select: { id: true, email: true, hasInstalledApp: true, signedInAt: true, hasReceivedInvitation: true, hasReceivedInvitationAt: true, phone: { select: { id: true, }, }, }, }, }, }, }, }) const data = await Promise.all( managingCompanies.map(async (managingCompany) => { const channels = await SendBirdController.getChannelsForUserId({ userId: managingCompany.id, }) return { name: managingCompany.shortName || managingCompany.longName, projects: await Promise.all( managingCompany.projects.map(async (project) => { const numberOfSMSNotifications = ( await prisma.notification.findMany({ where: { AND: [{ project: { id: project.id } }, { sentBySMS: true }] }, select: { id: true, }, }) ).length return { numberOfSMSNotifications, launchDate: project.launchDate ? moment(project.launchDate).format('LL') : '', numberOfProperties: project.numberOfProperties, name: project.name, projectColor: project.colorHex, projectLogoUrl: project.logo?.url, numberOfMemosSent: project.posts.length, numberOfMemosSentInThePastSevenDays: project.posts.filter( (p) => moment.duration(moment(p.createdAt).diff(moment())).asHours() > -24 * 7 ).length, numberOfMemosSentInThePastThirtyDays: project.posts.filter( (p) => moment.duration(moment(p.createdAt).diff(moment())).asHours() > -24 * 30 ).length, numberOfEvents: project.events.length, numberOfEventsCreatedInThePastSevenDays: project.events.filter( (event) => moment.duration(moment(event.createdAt).diff(moment())).asHours() > -24 * 7 ).length, numberOfEventsCreatedInThePastThirtyDays: project.events.filter( (event) => moment.duration(moment(event.createdAt).diff(moment())).asHours() > -24 * 30 ).length, numberOfConversations: channels.filter( (channel) => channel.name.split('-')[0] === project.id ).length, numberOfTasks: project.tasks.length, numberOfTasksCreatedInThePastSevenDays: project.tasks.filter( (serviceRequest) => moment.duration(moment(serviceRequest.createdAt).diff(moment())).asHours() > -24 * 7 ).length, numberOfTasksCreatedInThePastThirtyDays: project.tasks.filter( (serviceRequest) => moment.duration(moment(serviceRequest.createdAt).diff(moment())).asHours() > -24 * 30 ).length, numberOfPackages: project.packages.length, numberOfPackagesCreatedInThePastSevenDays: project.packages.filter( (aPackage) => moment.duration(moment(aPackage.createdAt).diff(moment())).asHours() > -24 * 7 ).length, numberOfPackagesCreatedInThePastThirtyDays: project.packages.filter( (aPackage) => moment.duration(moment(aPackage.createdAt).diff(moment())).asHours() > -24 * 30 ).length, numberOfAds: project.ads.length, numberOfAmenities: project.amenities.length, numberOfRules: project.rules.length, numberOfContacts: project.contacts.length, numberOfReservations: project.reservations.length, numberOfReservationsCreatedInThePastSevenDays: project.reservations.filter( (reservation) => moment.duration(moment(reservation.createdAt).diff(moment())).asHours() > -24 * 7 ).length, numberOfReservationsCreatedInThePastThirtyDays: project.reservations.filter( (reservation) => moment.duration(moment(reservation.createdAt).diff(moment())).asHours() > -24 * 30 ).length, numberOfFiles: project.folders.reduce((total, f) => (total += f.files.length), 0), numberOfFilesCreatedInThePastSevenDays: project.folders .map((f) => f.files) .flat() .filter( (file) => moment.duration(moment(file.createdAt).diff(moment())).asHours() > -24 * 7 ).length, numberOfFilesCreatedInThePastThirtyDays: project.folders .map((f) => f.files) .flat() .filter( (file) => moment.duration(moment(file.createdAt).diff(moment())).asHours() > -24 * 30 ).length, numberOfResidents: project.users.length, numberOfResidentsWithoutEmail: project.users.filter((u) => !u.email).length, numberOfResidentsWithoutPhoneNumber: project.users.filter((u) => !u.phone).length, numberOfResidentWithoutPhoneNumberAndEmail: project.users.filter( (u) => !u.phone && !u.email ).length, numberOfResidentsThatInstalledApp: project.users.filter((u) => u.hasInstalledApp) .length, numberOfResidentsThatReceivedInvitation: project.users.filter( (u) => u.hasReceivedInvitation || u.hasInstalledApp ).length, ...(await getPropertiesAndResidentsStatsForProject(project)), } }) ), } }) ) return res.status(200).json(managingCompanyId ? data[0] : data) } export default { // renderProjectsData, // getProjectsData, getManagingCompaniesData, getGlobalData, getManagingCompanyDetailedData, getPropertiesAndResidentsStatsForProject, PROJECT_ANALYTIC_FIELDS, }