import moment from 'moment' import * as Slack from '../../utils/slack' import { logger } from '../../utils/logger' import GeneralUtils from '../../utils/general' import * as SendBirdController from '../../controllers/sendbird' import { prismaClient as prisma } from '../../prismaClient' const DEFAULT_FOLDERS = [ { name: 'Assemblée / Assemblies' }, { name: 'Réunion conseil / Board meetings' }, { name: 'Budgets' }, { name: 'États financiers / Financial statements' }, { name: 'Assurance / Insurance certificate' }, { name: 'Registres et règlements / Declaration and servitude' }, ] const DEFAULT_SERVICES = [ 'TRAINER', 'TAILOR', 'CLEANING', 'MASSAGE', 'DINER', 'FLOWER', 'DOG', 'TICKET', 'CAR_WASH', 'SALE', 'HANDYMAN', 'GROCERY', 'PHARMACY', 'JUICE', 'CATERING', 'MEALBOX', 'DISINFECTION', 'MEDICAL', 'COFFEE', 'LOCAL_GROCERY', 'SELF_CARE', ] export async function createProject({ managingCompanyId, data }) { if (!managingCompanyId) { throw new Error(' Need managingCompanyId to create project') } if (!data) { throw new Error(' Need data to create project') } logger.info(`♻️ Creating project ${data.name || 'NO NAME'}`) const [allServices, managingCompany, mappedServices] = await Promise.all([ prisma.service.findMany({ where: {}, select: { id: true, }, }), prisma.managingCompany.findOne({ where: { id: managingCompanyId }, include: { projects: true, roles: { include: { projects: true, }, }, }, }), prisma.mappedService.findMany({ where: {} }), ]) logger.info('✅ Fetched all services, managing company and mapped services') const createdProject = await prisma.project.create({ data: { ...data, ...(data.name && !data.nameInitials && { nameInitials: GeneralUtils.getThreeLettersInitial(data.name), }), managingCompany: { connect: { id: managingCompanyId, }, }, folders: { create: { name: 'Root', }, }, thirdPartyServiceSettings: { create: mappedServices.map((mappedService) => ({ importEnabled: true, exportEnabled: true, mappedService: { connect: { id: mappedService.id, }, }, })), }, thirdPartyServices: { connect: mappedServices.map((mappedService) => ({ id: mappedService.id, })), }, tools: { set: [ 'NOTIFICATION', 'CALENDAR', 'CONTACT', 'RULE', 'AMENITY', 'MAIL', 'CHAT', 'USER', 'DELIVERY', 'WORK_ORDER', 'VALET', 'DOOR_ACCESS', 'SMART_SPEAKER', 'FILE', 'SALE', ], }, servicesAvailable: { set: DEFAULT_SERVICES, }, }, include: { folders: true, }, }) logger.info('✅ Created project') // CREATE SERVICE PROJECTS. for await (const service of allServices) { prisma.serviceProject.create({ data: { service: { connect: { id: service.id } }, project: { connect: { id: createdProject.id }, }, }, }) } logger.info('✅ Created service projects') await Promise.all([ // Create default segment prisma.segment.create({ data: { title: 'Owners', project: { connect: { id: createdProject.id, }, }, segmentFields: { create: [ { model: 'USER', comparison: 'EQUALS', path: 'residentType', value: 'owner', }, ], }, }, }), prisma.segment.create({ data: { title: 'Tenants', project: { connect: { id: createdProject.id, }, }, segmentFields: { create: [ { model: 'USER', comparison: 'EQUALS', path: 'residentType', value: 'tenant', }, ], }, }, }), prisma.segment.create({ data: { title: 'Board members', project: { connect: { id: createdProject.id, }, }, segmentFields: { create: [ { model: 'USER', comparison: 'EQUALS', path: 'residentType', value: 'boardMember', }, ], }, }, }), // Now create the default folders prisma.project.update({ where: { id: createdProject.id, }, data: { folders: { create: DEFAULT_FOLDERS.map((f) => ({ ...f, folderParent: { connect: { id: createdProject.folders[0].id, }, }, })), }, }, }), // Update the managing company roles that have already have all the projects Promise.all( managingCompany.roles .filter( (managingCompanyRole) => managingCompanyRole.projects.length >= Math.floor(managingCompany.projects.length / 2) ) .map((role) => prisma.managingCompanyRole.update({ where: { id: role.id, }, data: { projects: { connect: [ { id: createdProject.id, }, ], }, }, }) ) ), ]) logger.info('✅ Created managing company roles and segments') // TODO: uncomment when migration is done if (createdProject.condoManagerId) { Slack.send({ channel: 'newProjectCondoManager', subject: `New project synched with Condo Manager for ${ managingCompany.shortName || managingCompany.id }. Condo manager project id: ${createdProject.condoManagerId}`, }).catch(logger.error) } logger.info('Project created ✅') return createdProject } export const deleteProject = async (projectId = GeneralUtils.requiredParam('projectId')) => { logger.info(`♻️ Deleting project: ${projectId}`) await prisma.project.delete({ where: { id: projectId } }) const channels = await SendBirdController.getChannels({ all: true, name_contains: projectId, }) await Promise.all( channels.map((channel) => SendBirdController.deleteChannel({ channelUrl: channel.channel_url })) ) logger.info(`✅ Project ${projectId} deleted`) } type GetPendingActionsForProjectReturnType = { id: string name: string news: number calendar: number chat: number amenities: number marketPlace: number packages: number residents: number files: number contacts: number filters: number tasks: number condoManager?: number } export const getPendingActionsForProject = async ({ managingCompanyId, projectId, currentUserId, otherProjectIdsOfManagingCompany, }: { managingCompanyId: string projectId: string currentUserId: string otherProjectIdsOfManagingCompany: string[] }): Promise => { const project = await prisma.project.findOne({ where: { id: projectId }, include: { building: true, amenities: true, sharedAmenities: true, thirdPartyServiceSettings: { include: { mappedService: true, }, }, }, }) const [ tasksCount, amenitiesCount, channels, privateChannels, projectUsers, projectPackages, projectAds, ] = await Promise.all([ prisma.task .findMany({ where: { AND: [ { movedToCompleteListAt: null, }, { associatedProject: { id: project.id }, }, { assignedTo: null, }, { OR: [{ isArchived: false }, { isArchived: null }], }, ], }, }) .then((tasks) => tasks.length), prisma.reservation .findMany({ where: { status: 'PENDING', amenity: { id: { in: GeneralUtils.uniq( [ ...project.amenities, // Only show pending actions if the shared project is also part of this managing company ...project.sharedAmenities.filter((amenity) => otherProjectIdsOfManagingCompany.some((id) => id === amenity.projectId) ), ].map((amenity) => amenity.id), 'id' ), }, }, }, }) .then((reservations) => reservations.length), SendBirdController.getChannelsForUserId({ all: true, userId: managingCompanyId, unread_filter: 'unread_message', name_startswith: project.id, }), SendBirdController.getChannelsForUserId({ all: true, userId: currentUserId, unread_filter: 'unread_message', name_startswith: project.id, }), prisma.user.findMany({ where: { projects: { some: { id: { equals: projectId } } } }, select: { id: true, email: true, cantDeliverEmail: true, cantDeliverSMS: true, waitingForApproval: true, hasReceivedInvitation: true, hasInstalledApp: true, property: { select: { id: true, buildingId: true, }, }, properties: { select: { id: true, buildingId: true, }, }, phone: { select: { number: true, }, }, }, }), prisma.project .findOne({ where: { id: project.id }, select: { packages: { select: { id: true, status: true, }, }, }, }) .packages(), prisma.project .findOne({ where: { id: project.id }, select: { ads: { select: { id: true, status: true, }, }, }, }) .ads(), ]) const unreadMentionPrivateChannels = privateChannels.filter( (channel) => channel.unread_mention_count > 0 ) const projectUsersFormattedForProject = projectUsers.map((user) => { return { ...user, properties: user.properties.filter((property) => property.buildingId === project.buildingId), property: user.property?.buildingId === project.buildingId ? user.property : null, } }) const condoManagerSettingsForProject = project.thirdPartyServiceSettings.find( (tps) => tps.mappedService.friendlyName === 'Condo Manager' ) return { id: project.id, name: project.name, news: 0, calendar: 0, chat: GeneralUtils.uniq( [...channels, ...unreadMentionPrivateChannels].map((c) => c.name.replace('-private', '')) ).length, // Doing this because of "sharedAmenities" amenities: amenitiesCount, marketPlace: projectAds.filter(({ status }) => status === 'PENDING_APPROVAL').length, packages: projectPackages.filter(({ status }) => status === 'AT_RECEPTION').length, residents: projectUsersFormattedForProject.filter( (user) => user.waitingForApproval || (!user.property && user.properties && user.properties.length === 0) || // No units (!user.hasInstalledApp && !user.hasReceivedInvitation) || // Not invited (!user.email && !user.phone?.number) // No communcation channel ).length, files: 0, contacts: 0, filters: 0, tasks: tasksCount, condoManager: condoManagerSettingsForProject?.lastImport && moment.duration(moment().diff(moment(condoManagerSettingsForProject.lastImport))).asDays() > 5 ? 1 : 0, } }