Files
twenty_crm/packages/twenty-server/src/modules/contact-creation-manager/services/create-company-and-contact.service.ts
Charles Bochet a58236e6da Remove deprecated EMAIL, PHONE, LINK (#7551)
In this PR:
- remove deprecated EMAIL, PHONE, LINK field types (except for Zapier
package as there is another work ongoing)
- remove composite currency filter on currencyCode, actor filter on name
and workspaceMember as the UX is not great yet
2024-10-10 14:14:58 +02:00

215 lines
8.1 KiB
TypeScript

import { Injectable } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import chunk from 'lodash.chunk';
import compact from 'lodash.compact';
import { Any, EntityManager, Repository } from 'typeorm';
import { ObjectRecordCreateEvent } from 'src/engine/core-modules/event-emitter/types/object-record-create.event';
import { FieldActorSource } from 'src/engine/metadata-modules/field-metadata/composite-types/actor.composite-type';
import { FieldMetadataEntity } from 'src/engine/metadata-modules/field-metadata/field-metadata.entity';
import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity';
import { InjectObjectMetadataRepository } from 'src/engine/object-metadata-repository/object-metadata-repository.decorator';
import { TwentyORMGlobalManager } from 'src/engine/twenty-orm/twenty-orm-global.manager';
import { WorkspaceEventEmitter } from 'src/engine/workspace-event-emitter/workspace-event-emitter';
import { STANDARD_OBJECT_IDS } from 'src/engine/workspace-manager/workspace-sync-metadata/constants/standard-object-ids';
import { ConnectedAccountWorkspaceEntity } from 'src/modules/connected-account/standard-objects/connected-account.workspace-entity';
import { CONTACTS_CREATION_BATCH_SIZE } from 'src/modules/contact-creation-manager/constants/contacts-creation-batch-size.constant';
import { CreateCompanyService } from 'src/modules/contact-creation-manager/services/create-company.service';
import { CreateContactService } from 'src/modules/contact-creation-manager/services/create-contact.service';
import { Contact } from 'src/modules/contact-creation-manager/types/contact.type';
import { filterOutSelfAndContactsFromCompanyOrWorkspace } from 'src/modules/contact-creation-manager/utils/filter-out-contacts-from-company-or-workspace.util';
import { getDomainNameFromHandle } from 'src/modules/contact-creation-manager/utils/get-domain-name-from-handle.util';
import { getUniqueContactsAndHandles } from 'src/modules/contact-creation-manager/utils/get-unique-contacts-and-handles.util';
import { PersonWorkspaceEntity } from 'src/modules/person/standard-objects/person.workspace-entity';
import { WorkspaceMemberRepository } from 'src/modules/workspace-member/repositories/workspace-member.repository';
import { WorkspaceMemberWorkspaceEntity } from 'src/modules/workspace-member/standard-objects/workspace-member.workspace-entity';
import { isWorkEmail } from 'src/utils/is-work-email';
@Injectable()
export class CreateCompanyAndContactService {
constructor(
private readonly createContactService: CreateContactService,
private readonly createCompaniesService: CreateCompanyService,
@InjectObjectMetadataRepository(WorkspaceMemberWorkspaceEntity)
private readonly workspaceMemberRepository: WorkspaceMemberRepository,
private readonly workspaceEventEmitter: WorkspaceEventEmitter,
@InjectRepository(ObjectMetadataEntity, 'metadata')
private readonly objectMetadataRepository: Repository<ObjectMetadataEntity>,
@InjectRepository(FieldMetadataEntity, 'metadata')
private readonly fieldMetadataRepository: Repository<FieldMetadataEntity>,
private readonly twentyORMGlobalManager: TwentyORMGlobalManager,
) {}
private async createCompaniesAndPeople(
connectedAccount: ConnectedAccountWorkspaceEntity,
contactsToCreate: Contact[],
workspaceId: string,
source: FieldActorSource,
transactionManager?: EntityManager,
): Promise<DeepPartial<PersonWorkspaceEntity>[]> {
if (!contactsToCreate || contactsToCreate.length === 0) {
return [];
}
const personRepository =
await this.twentyORMGlobalManager.getRepositoryForWorkspace(
workspaceId,
PersonWorkspaceEntity,
);
const workspaceMembers =
await this.workspaceMemberRepository.getAllByWorkspaceId(
workspaceId,
transactionManager,
);
const contactsToCreateFromOtherCompanies =
filterOutSelfAndContactsFromCompanyOrWorkspace(
contactsToCreate,
connectedAccount,
workspaceMembers,
);
const { uniqueContacts, uniqueHandles } = getUniqueContactsAndHandles(
contactsToCreateFromOtherCompanies,
);
if (uniqueHandles.length === 0) {
return [];
}
const alreadyCreatedContacts = await personRepository.find({
where: {
emails: { primaryEmail: Any(uniqueHandles) },
},
});
const alreadyCreatedContactEmails: string[] = alreadyCreatedContacts?.map(
({ emails }) => emails?.primaryEmail,
);
const filteredContactsToCreate = uniqueContacts.filter(
(participant) =>
!alreadyCreatedContactEmails.includes(participant.handle) &&
participant.handle.includes('@'),
);
const filteredContactsToCreateWithCompanyDomainNames =
filteredContactsToCreate?.map((participant) => ({
handle: participant.handle,
displayName: participant.displayName,
companyDomainName: isWorkEmail(participant.handle)
? getDomainNameFromHandle(participant.handle)
: undefined,
}));
const domainNamesToCreate = compact(
filteredContactsToCreateWithCompanyDomainNames
.filter((participant) => participant.companyDomainName)
.map((participant) => ({
domainName: participant.companyDomainName,
createdBySource: source,
createdByWorkspaceMember: connectedAccount.accountOwner,
})),
);
const companiesObject = await this.createCompaniesService.createCompanies(
domainNamesToCreate,
workspaceId,
transactionManager,
);
const formattedContactsToCreate =
filteredContactsToCreateWithCompanyDomainNames.map((contact) => ({
handle: contact.handle,
displayName: contact.displayName,
companyId:
contact.companyDomainName && contact.companyDomainName !== ''
? companiesObject[contact.companyDomainName]
: undefined,
createdBySource: source,
createdByWorkspaceMember: connectedAccount.accountOwner,
}));
return this.createContactService.createPeople(
formattedContactsToCreate,
workspaceId,
transactionManager,
);
}
async createCompaniesAndContactsAndUpdateParticipants(
connectedAccount: ConnectedAccountWorkspaceEntity,
contactsToCreate: Contact[],
workspaceId: string,
source: FieldActorSource,
) {
const contactsBatches = chunk(
contactsToCreate,
CONTACTS_CREATION_BATCH_SIZE,
);
// TODO: Remove this when events are emitted directly inside TwentyORM
const objectMetadata = await this.objectMetadataRepository.findOne({
where: {
standardId: STANDARD_OBJECT_IDS.person,
workspaceId,
},
});
if (!objectMetadata) {
throw new Error('Object metadata not found');
}
// In some jobs the accountOwner is not populated
if (!connectedAccount.accountOwner) {
const workspaceMemberRepository =
await this.twentyORMGlobalManager.getRepositoryForWorkspace(
workspaceId,
WorkspaceMemberWorkspaceEntity,
);
const workspaceMember = await workspaceMemberRepository.findOne({
where: {
id: connectedAccount.accountOwnerId,
},
});
if (!workspaceMember) {
throw new Error(
`Workspace member with id ${connectedAccount.accountOwnerId} not found in workspace ${workspaceId}`,
);
}
connectedAccount.accountOwner = workspaceMember;
}
for (const contactsBatch of contactsBatches) {
const createdPeople = await this.createCompaniesAndPeople(
connectedAccount,
contactsBatch,
workspaceId,
source,
);
this.workspaceEventEmitter.emit(
'person.created',
createdPeople.map(
(createdPerson) =>
({
// FixMe: TypeORM typing issue... id is always returned when using save
recordId: createdPerson.id as string,
objectMetadata,
properties: {
after: createdPerson,
},
}) satisfies ObjectRecordCreateEvent<any>,
),
workspaceId,
);
}
}
}