import chunk from 'lodash/chunk' import moment from 'moment' import config from '../../../config' import { prismaClient as prisma } from '../../../prismaClient' import { condoManagerLogger } from '../../logger' export default async function processProperties({ buildingId, projectId, condoManagerProperties, buildingAddress, journalData, condoManagerComptes, }: { buildingId: string projectId: string condoManagerProperties: any[] buildingAddress: any journalData: any condoManagerComptes: any[] }) { if (config.thirdPartySync.logProcessAction) { condoManagerLogger.info('♻️ Processing properties') } const processCondoManagerProperty = async (condoManagerProperty, index) => { // For testing, when we want to process a specific unit // if (condoManagerProperty.NoCondo !== '1118') { // return // } if (config.thirdPartySync.logProcessAction) { condoManagerLogger.info(`♻️ Processing property: ${condoManagerProperty.NoCondo}`) } const condoManagerComptesForProperty = condoManagerComptes.filter( (c) => c.NoCondo === condoManagerProperty.NoCondo ) const activeCompteForProperty = condoManagerComptesForProperty.find((c) => c.Inactif === '0') const existingWalterProperty = ( await prisma.property.findMany({ take: 1, where: { building: { project: { id: projectId, }, }, address: { apartmentNumber: condoManagerProperty.NoCondo, }, }, }) )[0] if (!activeCompteForProperty) { if (existingWalterProperty) { // Maybe remove parkings/lockers/accountstatements/users/owners from this property condoManagerLogger.info( `No active compte for property ${condoManagerProperty.NoCondo} and property exists in Walter: ${existingWalterProperty.id}` ) } return } /** PARKINGS */ const parkingsDataForProperty = { create: [], connect: [], ...(existingWalterProperty && { update: [] }), ...(existingWalterProperty && { disconnect: [] }), } const existingWalterParkingsForProperty = existingWalterProperty ? await prisma.parking.findMany({ where: { properties: { some: { id: existingWalterProperty.id } } }, }) : [] // Loop of 9 because in CM you can only create 9 parkings for (let index = 1; index < 9; index++) { const condoManagerParkingNumber = condoManagerProperty[`NoGarage${index}`] const condoManagerParkingCadastreNumber = condoManagerProperty[`CadastreGarage${index}`] const condoManagerParkingProportionShare = condoManagerProperty[`TauxSurfaceGarage${index}`] // If the parking exists in CM if (condoManagerParkingNumber || condoManagerParkingCadastreNumber) { // We don't want to create the same parkings if we already created it const existingWalterParking = ( await prisma.parking.findMany({ take: 1, where: { AND: [ { project: { id: projectId, }, }, ...(condoManagerParkingCadastreNumber ? [ { cadastreNumber: condoManagerParkingCadastreNumber, }, ] : []), ...(condoManagerParkingNumber ? [ { number: condoManagerParkingNumber, }, ] : []), ], }, }) )[0] const parkingData = { number: condoManagerParkingNumber || condoManagerParkingCadastreNumber, cadastreNumber: condoManagerParkingCadastreNumber || '', proportionateShare: parseFloat(condoManagerParkingProportionShare), project: { connect: { id: projectId } }, } if (existingWalterParking) { // Update parking's data if it already exists in Walter if ( existingWalterProperty && existingWalterParkingsForProperty.some( (parking) => parking.id === existingWalterParking.id ) ) { parkingsDataForProperty.update = [ ...parkingsDataForProperty.update, { where: { id: existingWalterParking.id, }, data: parkingData, }, ] } // Else, just link it to the unit else { parkingsDataForProperty.connect = [ ...parkingsDataForProperty.connect, { id: existingWalterParking.id, }, ] } } // Else, create the parking and link it to the unit else { parkingsDataForProperty.create = [...parkingsDataForProperty.create, parkingData] } } } // Make sure that the parkings that we have are still valid existingWalterParkingsForProperty.forEach((parking) => { let stillValid for (let index = 1; index < 9; index++) { const condoManagerParkingNumber = condoManagerProperty[`NoGarage${index}`] const condoManagerParkingCadastreNumber = condoManagerProperty[`CadastreGarage${index}`] if (condoManagerParkingNumber && condoManagerParkingNumber === parking.number) { stillValid = true } else if ( condoManagerParkingCadastreNumber && condoManagerParkingCadastreNumber === parking.cadastreNumber ) { stillValid = true } } if (!stillValid) { condoManagerLogger.info( `Disconnecting parking ${parking.number} because it's no longer linked to this unit` ) parkingsDataForProperty.disconnect = [ ...parkingsDataForProperty.disconnect, { id: parking.id }, ] } }) if (parkingsDataForProperty.disconnect?.length === 0) { delete parkingsDataForProperty.disconnect } /** LOCKERS */ const lockersDataForProperty = { create: [], connect: [], ...(existingWalterProperty && { update: [] }), ...(existingWalterProperty && { disconnect: [] }), } const existingWalterLockersForProperty = existingWalterProperty ? await prisma.locker.findMany({ where: { properties: { some: { id: existingWalterProperty.id, }, }, }, }) : [] // Loop of 5 because in CM you can only create 5 lockers for (let index = 1; index < 5; index++) { const condoManagerLockerNumber = condoManagerProperty[`NoLocker${index}`] const condoManagerLockerCadastreNumber = condoManagerProperty[`CadastreLocker${index}`] // We don't want to create the same locker if we already created it const existingWalterLocker = ( await prisma.locker.findMany({ take: 1, where: { project: { id: projectId, }, ...(condoManagerLockerCadastreNumber && { cadastreNumber: condoManagerLockerCadastreNumber, }), ...(condoManagerLockerNumber && { number: condoManagerLockerNumber, }), }, }) )[0] // If the locker exists in CM if (condoManagerLockerNumber || condoManagerLockerCadastreNumber) { const lockerData = { number: condoManagerLockerNumber || condoManagerLockerCadastreNumber, cadastreNumber: condoManagerLockerCadastreNumber || '', project: { connect: { id: projectId } }, } if (existingWalterLocker) { // Update locker's data if it already exists in Walter if ( existingWalterProperty && existingWalterLockersForProperty.some((locker) => locker.id === existingWalterLocker.id) ) { lockersDataForProperty.update = [ ...lockersDataForProperty.update, { where: { id: existingWalterLocker.id, }, data: lockerData, }, ] } // Otherwise add the locker to the property else { lockersDataForProperty.connect = [ ...lockersDataForProperty.connect, { id: existingWalterLocker.id, }, ] } } // Otherwise create the locker and add it to the property else { lockersDataForProperty.create = [...lockersDataForProperty.create, lockerData] } } } // Make sure that the lockers that we have are still valid existingWalterLockersForProperty.forEach((locker) => { let stillValid for (let index = 1; index < 9; index++) { const condoManagerLockerNumber = condoManagerProperty[`NoLocker${index}`] const condoManagerLockerCadastreNumber = condoManagerProperty[`CadastreLocker${index}`] if (condoManagerLockerNumber && condoManagerLockerNumber === locker.number) { stillValid = true } else if ( condoManagerLockerCadastreNumber && condoManagerLockerCadastreNumber === locker.cadastreNumber ) { stillValid = true } } if (!stillValid) { condoManagerLogger.info( `Disconnecting locker ${locker.number} because it's no longer linked to this unit` ) lockersDataForProperty.disconnect = [ ...lockersDataForProperty.disconnect, { id: locker.id }, ] } }) if (lockersDataForProperty.disconnect?.length === 0) { delete lockersDataForProperty.disconnect } /** ACCOUNT STATEMENTS */ const accountStatementsForProperty = existingWalterProperty ? await prisma.accountStatement.findMany({ where: { property: { id: existingWalterProperty.id } }, }) : [] const accountStatementData = { create: [], ...(existingWalterProperty && { deleteMany: [] }), } // Delete wrong ones accountStatementsForProperty.forEach((accountStatement) => { const stillValid = accountStatement.accountNumber === activeCompteForProperty.NoCompteReel if (!stillValid) { if (config.thirdPartySync.logProcessAction) { condoManagerLogger.info( "Delete account statement since it's not linked to this compte anymore" ) } accountStatementData.deleteMany.push({ id: accountStatement.id }) } }) // Create good ones journalData .filter((journal) => activeCompteForProperty.NoCompteReel === journal.NoCompte) .forEach((journal) => { // Create only if it doesn't exist if ( !accountStatementsForProperty.some((accountStatement) => accountStatement.condoManagerId && journal.NoID ? accountStatement.condoManagerId === journal.NoID : accountStatement.receiptNumber && journal.NoFacture && accountStatement.transactionNumber && journal.NoTrx ? accountStatement.receiptNumber === journal.NoFacture && accountStatement.transactionNumber && journal.NoTrx : true ) ) { // if (config.thirdPartySync.logProcessAction) { condoManagerLogger.info('Adding a new account statement') // } accountStatementData.create.push({ condoManagerId: journal.NoID, transactionDate: moment(journal.DateTrx, 'MM/DD/YY HH:mm:ss').toDate(), transactionNumber: journal.NoTrx, amount: parseFloat(journal.Montant), accountNumber: journal.NoCompte, chequeNumber: journal.NoCheque, receiptNumber: journal.NoFacture, description: journal.Description, isDtCt: journal.DtCt === '1', isEndOfYear: journal.FinAnnee === '1', }) } }) const propertyData = { building: { connect: { id: buildingId, }, }, address: { [existingWalterProperty ? 'update' : 'create']: { ...buildingAddress, ...(condoManagerProperty['AdresseCopropriete1'] && { address1: condoManagerProperty['NoCivicCopropriete'] ? `${condoManagerProperty['NoCivicCopropriete']}, ${condoManagerProperty['AdresseCopropriete1']}` : condoManagerProperty['AdresseCopropriete1'], }), ...(condoManagerProperty['VilleCopropriete'] && { city: condoManagerProperty['VilleCopropriete'], }), ...(condoManagerProperty['CodePostalCopropriete'] && { zip: condoManagerProperty['CodePostalCopropriete'], }), ...(condoManagerProperty['ProvinceCopropriete'] && { state: condoManagerProperty['ProvinceCopropriete'], }), ...(condoManagerProperty['PaysCopropriete'] && { country: condoManagerProperty['PaysCopropriete'], }), apartmentNumber: condoManagerProperty.NoCondo, }, }, copropertyDueAmount: parseFloat( journalData .reduce((total, journal) => { if (activeCompteForProperty.NoCompteReel === journal.NoCompte) { total += parseFloat(journal.Montant) } return total }, 0) .toFixed(2) ), accountStatements: accountStatementData, proportionateShare: parseFloat(condoManagerProperty.TauxSurfaceCopropriete), squareFeet: parseFloat(condoManagerProperty.Superficie) || null, squareFeetBalcony: parseFloat(condoManagerProperty.SuperficieBalcon) || null, monthlyCondoFeeAmount: parseFloat(activeCompteForProperty?.MonthlyFee || 0) || null, parkings: parkingsDataForProperty, lockers: lockersDataForProperty, } if (config.thirdPartySync.logData) { condoManagerLogger.info(`Property data: ${JSON.stringify(propertyData, null, 2)}`) } if (config.thirdPartySync.writeData) { try { if (existingWalterProperty) { if (config.thirdPartySync.logCRUDAction) { condoManagerLogger.info(`Updating property ${existingWalterProperty.id}`) } await prisma.property.update({ where: { id: existingWalterProperty.id }, data: propertyData, }) } else { await prisma.property.create({ data: propertyData }) if (config.thirdPartySync.logCRUDAction) { condoManagerLogger.info( `🏠 Property created - ${propertyData.address.create.apartmentNumber}` ) } } } catch (error) { condoManagerLogger.info( `Error creating or updating property with data: ${JSON.stringify(propertyData, null, 2)}` ) condoManagerLogger.error(error) } } else { condoManagerLogger.info( `Would have create/update property with data: ${JSON.stringify(propertyData, null, 2)}` ) } if (config.thirdPartySync.logProcessAction) { condoManagerLogger.info(`♻️ Finish processing property: ${condoManagerProperty.NoCondo}`) } } let promise if (config.thirdPartySync.makeSyncFast) { const propertiesChunk = chunk(condoManagerProperties, 5) // @ts-ignore promise = propertiesChunk.reduce(async (promise, chunk) => { await promise await Promise.all(chunk.map(processCondoManagerProperty)) }, Promise.resolve) } else { promise = condoManagerProperties.reduce(async (promise, condoManagerProperty, index) => { await promise await processCondoManagerProperty(condoManagerProperty, index) }, Promise.resolve) } await promise if (config.thirdPartySync.logProcessAction) { condoManagerLogger.info('✅ Finish processing properties') } }