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
215 lines
8.1 KiB
TypeScript
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,
|
|
);
|
|
}
|
|
}
|
|
}
|