Skip to content

Commit

Permalink
Merge branch 'main' of https://github.com/panoratech/Panora into feat…
Browse files Browse the repository at this point in the history
…/jira+gorgias
  • Loading branch information
naelob committed Mar 28, 2024
2 parents 1399f8c + 2449e9f commit a20c9dd
Show file tree
Hide file tree
Showing 12 changed files with 664 additions and 700 deletions.
862 changes: 201 additions & 661 deletions LICENSE

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ import {
ClickupTeamInput,
ClickupTeamOutput,
} from '@ticketing/team/services/clickup/types';

import {
FrontAccountInput,
FrontAccountOutput,
Expand All @@ -14,7 +13,6 @@ import {
import { FrontAttachmentOutput } from '@ticketing/attachment/services/front/types';
import { GithubAttachmentOutput } from '@ticketing/attachment/services/github/types';
import { ZendeskAttachmentOutput } from '@ticketing/attachment/services/zendesk/types';

import {
FrontCommentInput,
FrontCommentOutput,
Expand Down
5 changes: 4 additions & 1 deletion packages/api/src/crm/company/company.module.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { AttioService } from './services/attio';
import { Module } from '@nestjs/common';
import { CompanyController } from './company.controller';
import { SyncService } from './sync/sync.service';
Expand All @@ -14,6 +15,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 +39,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

0 comments on commit a20c9dd

Please sign in to comment.