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

feat:Add integration with Close CRM #484

Merged
Prev Previous commit
Next Next commit
refactor:handled the feedback issue and refactored the code
  • Loading branch information
rajesh-jonnalagadda committed Jun 11, 2024
commit 07eaccf9e72a932c9b253807171a0a828b6e94d2
Original file line number Diff line number Diff line change
@@ -113,7 +113,7 @@ export class CloseConnectionService implements ICrmConnectionService {
provider_slug: 'close',
vertical: 'crm',
token_type: 'oauth',
account_url: CONNECTORS_METADATA['crm']['close'].urls.apiUrl,
account_url: CONNECTORS_METADATA['crm']?.close?.urls?.apiUrl,
access_token: this.cryptoService.encrypt(data.access_token),
refresh_token: this.cryptoService.encrypt(data.refresh_token),
expiration_timestamp: new Date(
4 changes: 2 additions & 2 deletions packages/api/src/crm/company/services/close/mappers.ts
Original file line number Diff line number Diff line change
@@ -77,9 +77,9 @@ export class CloseCompanyMapper implements ICompanyMapper {
}
}
let opts: any = {};
if (company?.created_by || company?.custom?.close_owner_id) {
if (company?.created_by) {
const owner_id = await this.utils.getUserUuidFromRemoteId(
(company?.created_by || company?.custom?.close_owner_id) as string,
company?.created_by as string,
'close',
);
if (owner_id) {
4 changes: 2 additions & 2 deletions packages/api/src/crm/company/services/close/types.ts
Original file line number Diff line number Diff line change
@@ -96,7 +96,7 @@ interface Opportunity {
lead_id: string;
}

interface Lead {
interface Company {
status_id: string;
status_label: string;
tasks: any[];
@@ -119,7 +119,7 @@ interface Lead {
description: string;
}

export type CloseCompanyOutput = Partial<Lead>;
export type CloseCompanyOutput = Partial<Company>;

export const commonCompanyCloseProperties = {
city: '',
27 changes: 20 additions & 7 deletions packages/api/src/crm/contact/services/close/mappers.ts
Original file line number Diff line number Diff line change
@@ -44,7 +44,11 @@ export class CloseContactMapper implements IContactMapper {
),
};

result.lead_id = source?.field_mappings?.['company_id'];
if (source.user_id) {
result.lead_id = await this.utils.getRemoteIdFromCompanyUuid(
source.user_id,
);
}

if (customFieldMappings && source.field_mappings) {
for (const [k, v] of Object.entries(source.field_mappings)) {
@@ -68,28 +72,36 @@ export class CloseContactMapper implements IContactMapper {
}[],
): Promise<UnifiedContactOutput | UnifiedContactOutput[]> {
if (!Array.isArray(source)) {
return this.mapSingleContactToUnified(source, customFieldMappings);
return await this.mapSingleContactToUnified(source, customFieldMappings);
}
// Handling array of CloseContactOutput
return source.map((contact) =>
this.mapSingleContactToUnified(contact, customFieldMappings),
return await Promise.all(
source.map((contact) =>
this.mapSingleContactToUnified(contact, customFieldMappings),
),
);
}

private mapSingleContactToUnified(
private async mapSingleContactToUnified(
contact: CloseContactOutput,
customFieldMappings?: {
slug: string;
remote_id: string;
}[],
): UnifiedContactOutput {
): Promise<UnifiedContactOutput> {
const field_mappings: { [key: string]: any } = {};
if (customFieldMappings) {
for (const mapping of customFieldMappings) {
field_mappings[mapping.slug] = contact[mapping.remote_id];
}
}

const opts: any = {};
if (contact.lead_id) {
opts.user_id = await this.utils.getCompanyUuidFromRemoteId(
contact.lead_id,
'close',
);
}
return {
remote_id: contact.id,
first_name: contact.name,
@@ -105,6 +117,7 @@ export class CloseContactMapper implements IContactMapper {
owner_type: 'contact',
})),
field_mappings,
...opts,
addresses: [],
};
}
4 changes: 2 additions & 2 deletions packages/api/src/crm/contact/services/close/types.ts
Original file line number Diff line number Diff line change
@@ -17,7 +17,7 @@ interface CustomFields {
[key: string]: string; // Allows dynamic keys for custom fields
}

interface LeadInput {
interface ContactInput {
lead_id: string;
name: string;
title: string;
@@ -54,7 +54,7 @@ interface Contact {
updated_by: string;
}

export type CloseContactInput = Partial<LeadInput>;
export type CloseContactInput = Partial<ContactInput>;

export type CloseContactOutput = Partial<Contact>;

104 changes: 46 additions & 58 deletions packages/api/src/crm/deal/services/close/mappers.ts
Original file line number Diff line number Diff line change
@@ -20,32 +20,33 @@ export class CloseDealMapper implements IDealMapper {
remote_id: string;
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Consider adding error handling for the asynchronous operations to ensure robustness.

}[],
): Promise<CloseDealInput> {
const emptyPromise = new Promise<string>((resolve) => {
return resolve('');
});
const promises = [];

promises.push(
source.company_id
? await this.utils.getRemoteIdFromCompanyUuid(source.company_id)
: emptyPromise,
);

promises.push(
source.stage_id
? await this.utils.getStageIdFromStageUuid(source.stage_id)
: emptyPromise,
);
const [lead_id, status_id] = await Promise.all(promises);
const result: CloseDealInput = {
note: source.description,
confidence: 0,
value: source.amount || 0,
value_period: 'one_time',
value_period: 'monthly',
custom: {},
lead_id: '',
lead_id,
status_id,
};

if (source.company_id) {
const lead_id = await this.utils.getRemoteIdFromCompanyUuid(
source.company_id,
);
if (lead_id) {
result.lead_id = lead_id;
}
}
if (source.stage_id) {
const stage_id = await this.utils.getStageIdFromStageUuid(
source.company_id,
);
if (stage_id) {
result.status_id = stage_id;
}
}

if (customFieldMappings && source.field_mappings) {
for (const [k, v] of Object.entries(source.field_mappings)) {
const mapping = customFieldMappings.find(
@@ -91,51 +92,38 @@ export class CloseDealMapper implements IDealMapper {
}
}

let opts: any = {};
if (deal.user_id) {
const owner_id = await this.utils.getUserUuidFromRemoteId(
deal.user_id,
'close',
);
if (owner_id) {
opts = {
...opts,
user_id: owner_id,
};
}
}
if (deal.lead_id) {
const lead_id = await this.utils.getCompanyUuidFromRemoteId(
deal.lead_id,
'close',
);
if (lead_id) {
opts = {
...opts,
company_id: lead_id,
};
}
}
if (deal.contact_id) {
const contact_id = await this.utils.getContactUuidFromRemoteId(
deal.contact_id,
'close',
);
if (contact_id) {
opts = {
...opts,
contact_id: contact_id,
};
}
}
const emptyPromise = new Promise<string>((resolve) => {
return resolve('');
});
const promises = [];

promises.push(
deal.user_id
? await this.utils.getUserUuidFromRemoteId(deal.user_id, 'close')
: emptyPromise,
);
promises.push(
deal.lead_id
? await this.utils.getCompanyUuidFromRemoteId(deal.lead_id, 'close')
: emptyPromise,
);
promises.push(
deal.status_id
? await this.utils.getCompanyUuidFromRemoteId(deal.status_id, 'close')
: emptyPromise,
);

const [user_id, company_id, stage_id] = await Promise.all(promises);

return {
remote_id: deal.id,
name: deal.note,
description: deal.note, // Placeholder if there's no direct mapping
amount: parseFloat(`${deal.value || 0}`),
//TODO; stage_id: deal.properties.dealstage,
field_mappings,
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Remove or implement the TODO comment for stage_id mapping.

...opts,
user_id,
company_id,
stage_id,
};
}
}
2 changes: 1 addition & 1 deletion packages/api/src/crm/stage/services/close/index.ts
Original file line number Diff line number Diff line change
@@ -41,7 +41,7 @@ export class CloseService implements IStageService {
const res = await this.prisma.crm_deals.findUnique({
where: { id_crm_deal: deal_id },
});
const baseURL = `${connection.account_url}/activity/status_change/opportunity/?opportunity_id=${res.remote_id}`;
const baseURL = `${connection.account_url}/activity/status_change/opportunity/?opportunity_id=${res?.remote_id}`;

const resp = await axios.get(baseURL, {
headers: {
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Return a meaningful error response or status code in the catch block to inform the caller of the failure.

58 changes: 19 additions & 39 deletions packages/api/src/crm/task/services/close/mappers.ts
Original file line number Diff line number Diff line change
@@ -88,53 +88,33 @@ export class CloseTaskMapper implements ITaskMapper {
field_mappings[mapping.slug] = task[mapping.remote_id];
}
}
let opts: any = {};
if (task.assigned_to) {
const owner_id = await this.utils.getUserUuidFromRemoteId(
task.assigned_to,
'close',
);
if (owner_id) {
opts = {
user_id: owner_id,
};
}
}
if (task.contact_id) {
const contact_id = await this.utils.getContactUuidFromRemoteId(
task.contact_id,
'close',
);
if (contact_id) {
opts = {
...opts,
contact_id: contact_id,
};
}
}
if (task.lead_id) {
const lead_id = await this.utils.getCompanyUuidFromRemoteId(
task.lead_id,
'close',
);
if (lead_id) {
opts = {
...opts,
company_id: lead_id,
};
}
}
const emptyPromise = new Promise<string>((resolve) => {
return resolve('');
});
const promises = [];

promises.push(
task.assigned_to
? await this.utils.getUserUuidFromRemoteId(task.assigned_to, 'close')
: emptyPromise,
);
promises.push(
task.lead_id
? await this.utils.getCompanyUuidFromRemoteId(task.lead_id, 'close')
: emptyPromise,
);
const [user_id, company_id] = await Promise.all(promises);

return {
remote_id: task.id,
subject: '',
content: task.text,
status: task?.is_complete ? 'COMPLETED' : 'PENDING',
due_date: new Date(task.due_date),
finished_date: task.finished_date ? new Date(task.finished_date) : '',
finished_date: task.finished_date ? new Date(task.finished_date) : null,
field_mappings,
...opts,
// Additional fields mapping based on UnifiedTaskOutput structure
user_id,
company_id,
};
}
}