/* eslint-disable dot-notation */ import fs from 'fs' import moment from 'moment' import config from '../../../config' import { prismaClient as prisma } from '../../../prismaClient' import GeneralUtils from '../../general' import { condoManagerLogger } from '../../logger' import updateManagingCompanyInformation from '../common/updateManagingCompanyInformation' import createMissingManagingCompanyUsers from '../common/createMissingManagingCompanyUsers' import { getDataFromLocalFiles, getDataFromFTPServer } from './getData' import processComptes from './processComptes' import processProperties from './processProperties' import updateProjectInformation from '../common/updateProjectInformation' import { checkDuplicatesAccountStatements, checkDuplicatesLockers, checkDuplicatesParkings, checkDuplicatesUnits, validateLockersData, validateParkingsData, validateResidentAreInGoodProperties, } from '../common/validateData' const thirdPartyFriendlyName = config.thirdPartySync.condoManager.friendlyName export default async function processProject({ managingCompanyId, projectId, }: { managingCompanyId: string projectId: string }) { try { const project = await prisma.project.findOne({ where: { id: projectId }, select: { id: true, name: true, condoManagerId: true, lastDataSyncDate: true, isLaunched: true, hasSyncedWithCondoManager: true, thirdPartyServiceSettings: { select: { id: true, importEnabled: true, importStatus: true, mappedService: { select: { friendlyName: true } }, }, }, thirdPartyServices: { select: { friendlyName: true, mappings: { select: { id: true, prismaSource: true, prismaSourceForeignKey: true, prismaSecondForeignKey: true, prismaSyncStatusField: true, excludeRecordIf: true, destinationKeyName: true, destinationPrimaryKey: true, destinationSecondPrimaryKey: true, prismaSourceDataType: true, destinationPrefixSuffix: true, prismaPrefixSuffix: true, requiresConfirmationToOverwriteWalter: true, requiresConfirmationToOverwriteThirdPartyService: true, }, }, }, }, building: { select: { id: true, address: { select: { address1: true, zip: true, city: true, state: true, country: true, countryCode: true, provinceCode: true, }, }, }, }, }, }) const managingCompany = await prisma.managingCompany.findOne({ where: { id: managingCompanyId }, select: { id: true, shortName: true, condoManagerId: true, users: { select: { email: true, }, }, }, }) condoManagerLogger.info(`♻️ Processing project: ${project.name}`) const condoManagerService = project.thirdPartyServices.find( (service) => service.friendlyName === thirdPartyFriendlyName ) const condoManagerServiceSettings = project.thirdPartyServiceSettings.find( (tps) => tps.mappedService.friendlyName === thirdPartyFriendlyName ) if (!condoManagerServiceSettings.importEnabled) { condoManagerLogger.info( 'Import not enabled for this project. Check the thirdPartyServiceSettings' ) return Promise.resolve() } const projectDir = GeneralUtils.removeAccents(project.name) .replace(/[^a-z0-9\s-]/gi, '') .replace(/\s+/g, '-') .toLowerCase() .trim() if (config.thirdPartySync.getDataFromFTPServer) { await getDataFromFTPServer({ managingCompanyCondoManagerId: managingCompany.condoManagerId, projectCondoManagerId: project.condoManagerId, projectDir, }) } const dataMapped = await getDataFromLocalFiles(projectDir) // Make sure the db we have is for the right project if ( dataMapped['Syndicat'] && String(dataMapped['Syndicat'][0]?.NoSyndicat) !== String(project.condoManagerId) ) { throw new Error( `The DB in FTP is not the right one! Project CM id is ${project.condoManagerId} and we have ${dataMapped['Syndicat'][0]?.NoSyndicat}` ) } const buildingAddress = dataMapped['Syndicat'] && dataMapped['Syndicat'][0] ? { address1: dataMapped['Syndicat'][0].NoCivique ? `${dataMapped['Syndicat'][0].NoCivique}, ${dataMapped['Syndicat'][0].Adresse}` : dataMapped['Syndicat'][0].Adresse, zip: dataMapped['Syndicat'][0].CodePostal, state: dataMapped['Syndicat'][0].Province, city: dataMapped['Syndicat'][0].Ville, country: dataMapped['Syndicat'][0].Pays, } : {} // Write data for fun if (process.env.NODE_ENV === 'development') { Object.keys(dataMapped).forEach((key) => { fs.writeFile( `condomanager/${projectDir}/${key}.json`, JSON.stringify(dataMapped[key], null, 2), () => null ) }) } // Type 7 are residents const comptesOwners = dataMapped['Comptes']?.filter((compte) => compte.TypeCompte === '7') || [] condoManagerLogger.info(`♻️ Processing ${comptesOwners.length} comptes owners`) // "Type compte === 2" and "type compte à recevoir === 1" sont locataires const comptesTenants = dataMapped['Comptes']?.filter((c) => c.TypeCompte === '2' && c.TypeCompteARecevoir === '1') || [] condoManagerLogger.info(`♻️ Processing ${comptesTenants.length} comptes tenants`) // Occupants (If not exist in Walter, we put it as a tenant) const comptesOccupants = [] // dataMapped['Occupants'] || [] condoManagerLogger.info(`♻️ Processing ${comptesOccupants.length} comptes occupants`) // We sometimes have empty object at the end const validCoproprietes = dataMapped['Coproprietes'] ? dataMapped['Coproprietes'].filter((copropriete) => !!copropriete.NoCondo) : [] condoManagerLogger.info(`♻️ Processing ${validCoproprietes.length} coproprietes`) const allValidComptes = [...comptesOwners, ...comptesTenants, ...comptesOccupants] // ONLY DO THIS IF IT'S NEW PROJECT // COMPANY MIGHT CHANGED PROJECT NAME AND WE DON'T WANT TO OVERIDE IT EVERY TIME if ( managingCompany.shortName === 'Managing company ABC' || !project.hasSyncedWithCondoManager || project.name.substring(0, 3) === 'Ass' ) { const condoManagerManagingCompanyData = dataMapped['CompagnieGestion']?.length ? dataMapped['CompagnieGestion'][0] : null if (condoManagerManagingCompanyData) { await updateManagingCompanyInformation({ managingCompanyId, shortName: condoManagerManagingCompanyData.NomGestionnaire, longName: condoManagerManagingCompanyData.NomGestionnaire, email: condoManagerManagingCompanyData.Courriel, mainPhoneNumber: condoManagerManagingCompanyData.Telephone, emergencyPhoneNumber: condoManagerManagingCompanyData.Telephone, address1: condoManagerManagingCompanyData.Adresse1, city: condoManagerManagingCompanyData.Ville, state: condoManagerManagingCompanyData.Province, country: condoManagerManagingCompanyData.Pays, zip: condoManagerManagingCompanyData.CodePostal, }) } if (managingCompany.users[0]?.email?.includes('usewalter.com')) { await createMissingManagingCompanyUsers({ managingCompanyId: managingCompany.id, condoManagerUsers: dataMapped['Usagers'], }) } if (dataMapped['Syndicat'][0] && !project.hasSyncedWithCondoManager) { await updateProjectInformation({ projectId, apartmentNumber: dataMapped['Syndicat'][0].NoApt, address1: dataMapped['Syndicat'][0].Adresse, zip: dataMapped['Syndicat'][0].CodePostal, state: dataMapped['Syndicat'][0].Province, city: dataMapped['Syndicat'][0].Ville, country: dataMapped['Syndicat'][0].Pays, numberOfProperties: parseFloat(dataMapped['Syndicat'][0].UnitCount), name: dataMapped['Syndicat'][0].Nom, }) } } if (config.thirdPartySync.validateData) { await checkDuplicatesParkings({ projectId: project.id }) await checkDuplicatesLockers({ projectId: project.id }) await checkDuplicatesUnits({ projectId: project.id }) await checkDuplicatesAccountStatements({ projectId: project.id }) await validateLockersData({ projectId: project.id, condoManagerLockers: validCoproprietes.reduce((acc, cmUnit) => { const lockers = [] for (let index = 1; index < 5; index++) { const number = cmUnit[`NoLocker${index}`] const cadastre = cmUnit[`CadastreLocker${index}`] if (number) { lockers.push({ number, cadastre, }) } } return [...acc, ...lockers] }, []), }) await validateParkingsData({ projectId: project.id, condoManagerParkings: validCoproprietes.reduce((acc, cmUnit) => { const parkings = [] for (let index = 1; index < 5; index++) { const number = cmUnit[`NoGarage${index}`] const cadastre = cmUnit[`CadastreGarage${index}`] if (number) { parkings.push({ number, cadastre, }) } } return [...acc, ...parkings] }, []), }) await validateResidentAreInGoodProperties({ projectId: project.id, condoManagerUnitWithResidents: validCoproprietes.map((copropriete) => ({ unitNumber: copropriete.NoCondo, residents: allValidComptes .filter((compte) => compte.NoCondo === copropriete.NoCondo) .reduce((acc, compte) => { const residents = [] if ( compte.Courriel || compte.TelCellulaire || compte.TelBureau || compte.TelResidence ) { residents.push({ email: compte.Courriel, mobilePhone: compte.TelCellulaire, homePhone: compte.TelResidence, workPhone: compte.TelBureau, }) } if ( compte.Courriel2 || compte.TelCellulaireNom2 || compte.TelBureauNom2 || compte.TelResidence2 ) { residents.push({ email: compte.Courriel2, mobilePhone: compte.TelCellulaireNom2, homePhone: compte.TelResidence2, workPhone: compte.TelBureauNom2, }) } return [...acc, ...residents] }, []), })), }) } if (config.thirdPartySync.processProperties) { await processProperties({ buildingAddress, buildingId: project.building.id, projectId: project.id, condoManagerProperties: validCoproprietes, journalData: dataMapped['Journal'], condoManagerComptes: allValidComptes, }) } if (config.thirdPartySync.processResidents) { await processComptes({ condoManagerService, projectId: project.id, comptes: allValidComptes, condoManagerProperties: validCoproprietes, }) } // Update both for now until everyone move to API integration await prisma.project.update({ where: { id: project.id }, data: { lastDataSyncDate: moment().toDate(), hasSyncedWithCondoManager: true }, }) await prisma.mappedServiceSetting.update({ where: { id: condoManagerServiceSettings.id }, data: { importStatus: 'Data were successfully imported', lastImport: new Date(), }, }) condoManagerLogger.info( `🏠 Processed ${validCoproprietes.length} properties for ${project.name}` ) condoManagerLogger.info(`👤 Processed ${allValidComptes.length} comptes for ${project.name}`) condoManagerLogger.info(`✅ Done processing project: ${project.name}`) if (!config.thirdPartySync.makeSyncFast) { condoManagerLogger.info('Wait 5 seconds before processing next project...') await GeneralUtils.wait(5000) } } catch (error) { condoManagerLogger.error(error) } return Promise.resolve() }