Skip to content

Commit

Permalink
feat: add deal object
Browse files Browse the repository at this point in the history
  • Loading branch information
Iamsidar07 committed Aug 19, 2024
1 parent 941ced2 commit 33154c0
Show file tree
Hide file tree
Showing 3 changed files with 316 additions and 0 deletions.
117 changes: 117 additions & 0 deletions packages/api/src/crm/deal/services/leadsquared/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
import { Injectable } from '@nestjs/common';
import { IDealService } from '@crm/deal/types';
import { CrmObject } from '@crm/@lib/@types';
import { LeadSquaredDealInput, LeadSquaredDealOutput } from './types';
import axios from 'axios';
import { PrismaService } from '@@core/@core-services/prisma/prisma.service';
import { LoggerService } from '@@core/@core-services/logger/logger.service';
import { ActionType, handle3rdPartyServiceError } from '@@core/utils/errors';
import { EncryptionService } from '@@core/@core-services/encryption/encryption.service';
import { ApiResponse } from '@@core/utils/types';
import { ServiceRegistry } from '../registry.service';
import { SyncParam } from '@@core/utils/types/interface';

@Injectable()
export class LeadSquaredService implements IDealService {
constructor(
private prisma: PrismaService,
private logger: LoggerService,
private cryptoService: EncryptionService,
private registry: ServiceRegistry,
) {
this.logger.setContext(
CrmObject.deal.toUpperCase() + ':' + LeadSquaredService.name,
);
this.registry.registerService('leadsquared', this);
}

async addDeal(
dealData: LeadSquaredDealInput,
linkedUserId: string,
): Promise<ApiResponse<LeadSquaredDealOutput>> {
try {
const connection = await this.prisma.connections.findFirst({
where: {
id_linked_user: linkedUserId,
provider_slug: 'leadsquared',
vertical: 'crm',
},
});
const resp = await axios.post(
`${connection.account_url}/v2/OpportunityManagement.svc/Capture`,
dealData,
{
headers: {
'Content-Type': 'application/json',
'x-LSQ-AccessKey': this.cryptoService.decrypt(
connection.access_token,
),
'x-LSQ-SecretKey': this.cryptoService.decrypt(
connection.secret_token,
),
},
},
);
const opportunityId = resp.data.CreatedOpportunityId;
const opportunityResp = await axios.get(
`${connection.account_url}/v2/OpportunityManagement.svc/GetOpportunityDetails?OpportunityId=${opportunityId}`,
{
headers: {
'x-LSQ-AccessKey': this.cryptoService.decrypt(
connection.access_token,
),
'x-LSQ-SecretKey': this.cryptoService.decrypt(
connection.secret_token,
),
},
},
);
return {
data: opportunityResp.data,
message: 'Leadsquared deal created',
statusCode: 201,
};
} catch (error) {
throw error;
}
}

async sync(data: SyncParam): Promise<ApiResponse<LeadSquaredDealOutput[]>> {
try {
// TODO: I'm not sure about this
const { linkedUserId, leadId, opportunityType } = data;
const connection = await this.prisma.connections.findFirst({
where: {
id_linked_user: linkedUserId,
provider_slug: 'pipedrive',
vertical: 'crm',
},
});
let url = `${connection.account_url}/v2/OpportunityManagement.svc/GetOpportunitiesOfLead?leadId=${leadId}`;

if (opportunityType) {
url += `&opportunityType=${opportunityType}`;
}

const resp = await axios.post(url, {
headers: {
'Content-Type': 'application/json',
'x-LSQ-AccessKey': this.cryptoService.decrypt(
connection.access_token,
),
'x-LSQ-SecretKey': this.cryptoService.decrypt(
connection.secret_token,
),
},
});

return {
data: resp.data['List'],
message: 'Leadsquared deals retrieved',
statusCode: 200,
};
} catch (error) {
throw error;
}
}
}
147 changes: 147 additions & 0 deletions packages/api/src/crm/deal/services/leadsquared/mappers.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,147 @@
import { LeadSquaredDealInput, LeadSquaredDealOutput } from './types';
import {
UnifiedCrmDealInput,
UnifiedCrmDealOutput,
} from '@crm/deal/types/model.unified';
import { IDealMapper } from '@crm/deal/types';
import { Utils } from '@crm/@lib/@utils';
import { MappersRegistry } from '@@core/@core-services/registries/mappers.registry';
import { Injectable } from '@nestjs/common';
import { IngestDataService } from '@@core/@core-services/unification/ingest-data.service';
import { CrmObject } from '@crm/@lib/@types';
import { UnifiedCrmStageOutput } from '@crm/stage/types/model.unified';
import { ZohoStageOutput } from '@crm/stage/services/zoho/types';

@Injectable()
export class LeadSquaredDealMapper implements IDealMapper {
constructor(
private mappersRegistry: MappersRegistry,
private utils: Utils,
private ingestService: IngestDataService,
) {
this.mappersRegistry.registerService('crm', 'deal', 'leadsquared', this);
}
async desunify(
source: UnifiedCrmDealInput,
customFieldMappings?: {
slug: string;
remote_id: string;
}[],
): Promise<LeadSquaredDealInput> {
const result: LeadSquaredDealInput = {
OpportunityNote: source.description,
OpportunityName: source.name,
Amount: source.amount ?? 0,
};
if (source.company_id) {
result.Account_Name = {
id: await this.utils.getRemoteIdFromCompanyUuid(source.company_id),
name: await this.utils.getCompanyNameFromUuid(source.company_id),
};
}
if (source.stage_id) {
result.Stage = await this.utils.getStageNameFromStageUuid(
source.stage_id,
);
}
if (source.user_id) {
result.Owner = {
id: await this.utils.getRemoteIdFromUserUuid(source.user_id),
} as any;
}

if (customFieldMappings && source.field_mappings) {
for (const [k, v] of Object.entries(source.field_mappings)) {
const mapping = customFieldMappings.find(
(mapping) => mapping.slug === k,
);
if (mapping) {
result[mapping.remote_id] = v;
}
}
}

return result;
}

async unify(
source: LeadSquaredDealOutput | LeadSquaredDealOutput[],
connectionId: string,
customFieldMappings?: {
slug: string;
remote_id: string;
}[],
): Promise<UnifiedCrmDealOutput | UnifiedCrmDealOutput[]> {
if (!Array.isArray(source)) {
return await this.mapSingleDealToUnified(
source,
connectionId,
customFieldMappings,
);
}

return Promise.all(
source.map((deal) =>
this.mapSingleDealToUnified(deal, connectionId, customFieldMappings),
),
);
}

private async mapSingleDealToUnified(
deal: LeadSquaredDealOutput,
connectionId: string,
customFieldMappings?: {
slug: string;
remote_id: string;
}[],
): Promise<UnifiedCrmDealOutput> {
const field_mappings: { [key: string]: any } = {};
if (customFieldMappings) {
for (const mapping of customFieldMappings) {
field_mappings[mapping.slug] = deal[mapping.remote_id];
}
}
const res: UnifiedCrmDealOutput = {
remote_id: deal.OpportunityId,
remote_data: deal,
name: deal.OpportunityNote,
description: deal.OpportunityNote ?? '', // todo null
amount: deal.Amount,
field_mappings,
};

if (deal.Stage) {
// we insert right way inside our db as there are no endpoint to do so in the Zoho api
const stage = await this.ingestService.ingestData<
UnifiedCrmStageOutput,
ZohoStageOutput
>(
[
{
Stage_Name: deal.Stage,
},
],
'leadsquared',
connectionId,
'crm',
CrmObject.stage,
[],
);
res.stage_id = stage[0].id_crm_deals_stage;
}

if (deal.ProspectId) {
res.user_id = await this.utils.getUserUuidFromRemoteId(
deal.ProspectId,
connectionId,
);
}
if (deal.LeadOwner) {
res.company_id = await this.utils.getCompanyUuidFromRemoteId(
deal.LeadOwner,
connectionId,
);
}
return res;
}
}
52 changes: 52 additions & 0 deletions packages/api/src/crm/deal/services/leadsquared/types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
type LeadDetail = {
Attribute: string;
Value: string;
};
// If comments are enabled on opportunity status change, you must pass the mx_Custom_19 (of datatype ‘String’)
type Field = {
SchemaName: string;
Value: string;
};

type Opportunity = {
Filelds: Field[];
OpportunityEventCode: number;
OpportunityNote?: string;
OpportunityDateTime?: string; //date and time is in the yyyy-mm-dd hh:mm:ss format.
OverwriteFields?: boolean;
UpdateEmptyFields?: boolean;
DoNotPostDuplicateActivity?: boolean;
DoNotChangeOwner?: boolean;
};

export type LeadSquaredDealInput = {
LeadDetails: LeadDetail[]; // atleast 1 unique field is required. Attribute 'SearchBy' is required
Opportunity: Opportunity;
};

export type LeadSquaredDealOutput = {
ProspectId: string;
FirstName: string;
LastName: string;
EmailAddress: string;
Phone: string;
DoNotCall: '0' | '1';
DoNotEmail: '0' | '1';
LeadName: string;
LeadOwner: string;
OpportunityEventType: string;
OpportunityEvent: string;
OpportunityNote: string;
Score: string;
PACreatedOn: string; //'2020-09-16 05:43:00';
PAModifiedOn: string; //'2020-09-16 07:26:09';
IP_Latitude: string | null;
IP_Longitude: string | null;
PACreatedByName: string;
Status: string;
Owner: string;
OwnerName: string;
OpportunityId: string;
Total: string;
[key: string]: string | number | null;
};

0 comments on commit 33154c0

Please sign in to comment.