Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Attio company object added #331

Merged
merged 2 commits into from
Mar 27, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion packages/api/src/crm/@utils/@types/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -159,7 +159,7 @@ export * from '../../company/services/zendesk/types';
export * from '../../company/services/hubspot/types';
export * from '../../company/services/zoho/types';
export * from '../../company/services/pipedrive/types';

export * from '../../company/services/attio/types'
/* engagementType */

export class Email {
Expand Down
4 changes: 3 additions & 1 deletion packages/api/src/crm/company/company.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import { HubspotService } from './services/hubspot';
import { PipedriveService } from './services/pipedrive';
import { ZendeskService } from './services/zendesk';
import { ZohoService } from './services/zoho';
import { AttioService } from './services/attio'

@Module({
imports: [
Expand All @@ -37,7 +38,8 @@ import { ZohoService } from './services/zoho';
ZohoService,
PipedriveService,
HubspotService,
AttioService
],
exports: [SyncService],
})
export class CompanyModule {}
export class CompanyModule { }
109 changes: 109 additions & 0 deletions packages/api/src/crm/company/services/attio/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
/* eslint-disable @typescript-eslint/no-unused-vars */
import { Injectable } from '@nestjs/common';
import axios from 'axios';
import {
CrmObject,
AttioCompanyInput,
AttioCompanyOutput,
} from '@crm/@utils/@types';
import { PrismaService } from '@@core/prisma/prisma.service';
import { LoggerService } from '@@core/logger/logger.service';
import { ActionType, handleServiceError } from '@@core/utils/errors';
import { EncryptionService } from '@@core/encryption/encryption.service';
import { ApiResponse } from '@@core/utils/types';
import { ICompanyService } from '@crm/company/types';
import { ServiceRegistry } from '../registry.service';

@Injectable()
export class AttioService implements ICompanyService {
constructor(
private prisma: PrismaService,
private logger: LoggerService,
private cryptoService: EncryptionService,
private registry: ServiceRegistry,
) {
this.logger.setContext(
CrmObject.company.toUpperCase() + ':' + AttioService.name,
);
this.registry.registerService('attio', this);
}

async addCompany(
companyData: AttioCompanyInput,
linkedUserId: string,
): Promise<ApiResponse<AttioCompanyOutput>> {
try {
const connection = await this.prisma.connections.findFirst({
where: {
id_linked_user: linkedUserId,
provider_slug: 'attio',
},
});

const resp = await axios.post(
'https://api.attio.com/v2/objects/companies/records',
JSON.stringify({
data: companyData
}),
{
headers: {
Authorization: `Bearer ${this.cryptoService.decrypt(
connection.access_token,
)}`,
'Content-Type': 'application/json',
},
},
);
return {
data: resp.data.data,
message: 'Attio company created',
statusCode: 201,
};
} catch (error) {
handleServiceError(
error,
this.logger,
'Attio',
CrmObject.company,
ActionType.POST,
);
}
}

async syncCompanies(
linkedUserId: string,
): Promise<ApiResponse<AttioCompanyOutput[]>> {
try {
const connection = await this.prisma.connections.findFirst({
where: {
id_linked_user: linkedUserId,
provider_slug: 'attio',
},
});
const resp = await axios.post(
`https://api.attio.com/v2/objects/companies/records/query`, {},
{
headers: {
'Content-Type': 'application/json',
Authorization: `Bearer ${this.cryptoService.decrypt(
connection.access_token,
)}`,
},
},
);
return {
data: resp.data.data,
message: 'Attio companies retrieved',
statusCode: 200,
};
} catch (error) {
handleServiceError(
error,
this.logger,
'Attio',
CrmObject.company,
ActionType.POST,
);
}
}
}
167 changes: 167 additions & 0 deletions packages/api/src/crm/company/services/attio/mappers.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,167 @@
import { AttioCompanyInput, AttioCompanyOutput } from '@crm/@utils/@types';
import {
UnifiedCompanyInput,
UnifiedCompanyOutput,
} from '@crm/company/types/model.unified';
import { ICompanyMapper } from '@crm/company/types';
import { Utils } from '@crm/deal/utils';

export class AttioCompanyMapper implements ICompanyMapper {
private readonly utils: Utils;

constructor() {
this.utils = new Utils();
}
async desunify(
source: UnifiedCompanyInput,
customFieldMappings?: {
slug: string;
remote_id: string;
}[],
): Promise<AttioCompanyInput> {
const result: AttioCompanyInput = {
values: {
name: [{
value: source.name
}],
categories: [{
option: source.industry
}]
}
}
// const result: AttioCompanyInput = {
// city: '',
// name: source.name,
// phone: '',
// state: '',
// domain: '',
// industry: source.industry,
// };

// Assuming 'phone_numbers' array contains at least one phone number
// const primaryPhone = source.phone_numbers?.[0]?.phone_number;
// if (primaryPhone) {
// result.values = primaryPhone;
// }
if (source.addresses) {
const address = source.addresses[0];
if (address) {
// result.city = address.city;
// result.state = address.state;
result.values.primary_location = [{
locality: address.city,
line_1: address.street_1,
line_2: address.street_2,
line_3: null,
line_4: null,
region: address.state + "," + address.country,
postcode: address.postal_code,
latitude: null,
longitude: null,
country_code: null,
}]
}
}

if (source.user_id) {
const owner_id = await this.utils.getRemoteIdFromUserUuid(source.user_id);
if (owner_id) {
result.values.team = [{
target_object: 'people',
target_record_id: owner_id,
}];
}
}

// Attio company does not have attribute for email address
// Attio Company doest not have direct mapping of number of employees



if (customFieldMappings && source.field_mappings) {
for (const fieldMapping of source.field_mappings) {
for (const key in fieldMapping) {
const mapping = customFieldMappings.find(
(mapping) => mapping.slug === key,
);
if (mapping) {
result.values[mapping.remote_id] = fieldMapping[key];
}
}
}
}

return result;
}

async unify(
source: AttioCompanyOutput | AttioCompanyOutput[],
customFieldMappings?: {
slug: string;
remote_id: string;
}[],
): Promise<UnifiedCompanyOutput | UnifiedCompanyOutput[]> {
if (!Array.isArray(source)) {
return this.mapSingleCompanyToUnified(source, customFieldMappings);
}
// Handling array of AttioCompanyOutput
return Promise.all(
source.map((company) =>
this.mapSingleCompanyToUnified(company, customFieldMappings),
),
);
}

private async mapSingleCompanyToUnified(
company: AttioCompanyOutput,
customFieldMappings?: {
slug: string;
remote_id: string;
}[],
): Promise<UnifiedCompanyOutput> {
const field_mappings =
customFieldMappings?.map((mapping) => ({
[mapping.slug]: company.values[mapping.remote_id],
})) || [];

let opts: any = {};

if (company.values.team[0]?.target_record_id) {
const owner_id = await this.utils.getUserUuidFromRemoteId(
company.values.team[0].target_record_id,
'attio',
);
if (owner_id) {
opts = {
user_id: owner_id,
};
}
}

return {
name: company.values.name[0]?.value,
industry: typeof company.values.categories[0]?.option === "string" ? company.values.categories[0]?.option : company.values.categories[0]?.option.title,
number_of_employees: 0, // Placeholder, as there's no direct mapping provided
addresses: [
{
street_1: company.values.primary_location[0]?.line_1,
city: company.values.primary_location[0]?.locality,
state: company.values.primary_location[0]?.region,
postal_code: company.values.primary_location[0]?.postcode,
country: company.values.primary_location[0]?.country_code,
address_type: 'primary',
owner_type: 'company',
},
], // Assuming 'street', 'city', 'state', 'postal_code', 'country' are properties in company.properties
phone_numbers: [
{
phone_number: '',
phone_type: 'primary',
owner_type: 'company',
},
],
field_mappings,
...opts,
};
}
}
Loading
Loading