From 80376ef5233c189ae80fc291ed03e71891dbab3a Mon Sep 17 00:00:00 2001 From: nael Date: Tue, 16 Jul 2024 00:50:43 +0200 Subject: [PATCH] :bug: Fix write unified for contact --- packages/api/src/@core/utils/types/index.ts | 213 ++++++++++++++++++ packages/api/src/crm/@lib/@utils/index.ts | 22 +- .../crm/company/services/pipedrive/index.ts | 15 +- .../api/src/crm/contact/contact.module.ts | 10 +- .../src/crm/contact/services/attio/mappers.ts | 68 ++++-- .../src/crm/contact/services/close/mappers.ts | 7 +- .../crm/contact/services/contact.service.ts | 12 +- .../crm/contact/services/hubspot/mappers.ts | 47 +++- .../src/crm/contact/services/hubspot/types.ts | 26 ++- .../crm/contact/services/pipedrive/mappers.ts | 25 +- .../src/crm/contact/services/zoho/index.ts | 15 +- .../src/crm/contact/services/zoho/mappers.ts | 15 +- .../engagement/services/pipedrive/index.ts | 20 +- .../engagement/services/pipedrive/mappers.ts | 140 +++++++++++- .../src/crm/engagement/services/zoho/index.ts | 50 +--- .../api/src/crm/engagement/types/index.ts | 2 +- .../api/src/crm/task/services/zoho/index.ts | 16 +- 17 files changed, 555 insertions(+), 148 deletions(-) diff --git a/packages/api/src/@core/utils/types/index.ts b/packages/api/src/@core/utils/types/index.ts index 2db69b3d6..3ff05148c 100644 --- a/packages/api/src/@core/utils/types/index.ts +++ b/packages/api/src/@core/utils/types/index.ts @@ -161,3 +161,216 @@ export const MIME_TYPES: { [key: string]: string } = { '.3g2': 'video/3gpp2', '.7z': 'application/x-7z-compressed', }; + +export const COUNTRY_CODES: { [key: string]: string } = { + Afghanistan: 'AF', + Albania: 'AL', + Algeria: 'DZ', + Andorra: 'AD', + Angola: 'AO', + Antigua_and_Barbuda: 'AG', + Argentina: 'AR', + Armenia: 'AM', + Australia: 'AU', + Austria: 'AT', + Azerbaijan: 'AZ', + Bahamas: 'BS', + Bahrain: 'BH', + Bangladesh: 'BD', + Barbados: 'BB', + Belarus: 'BY', + Belgium: 'BE', + Belize: 'BZ', + Benin: 'BJ', + Bhutan: 'BT', + Bolivia: 'BO', + Bosnia_and_Herzegovina: 'BA', + Botswana: 'BW', + Brazil: 'BR', + Brunei: 'BN', + Bulgaria: 'BG', + Burkina_Faso: 'BF', + Burundi: 'BI', + Cambodia: 'KH', + Cameroon: 'CM', + Canada: 'CA', + Cape_Verde: 'CV', + Central_African_Republic: 'CF', + Chad: 'TD', + Chile: 'CL', + China: 'CN', + Colombia: 'CO', + Comoros: 'KM', + Congo: 'CG', + Costa_Rica: 'CR', + Croatia: 'HR', + Cuba: 'CU', + Cyprus: 'CY', + Czech_Republic: 'CZ', + Denmark: 'DK', + Djibouti: 'DJ', + Dominica: 'DM', + Dominican_Republic: 'DO', + East_Timor: 'TL', + Ecuador: 'EC', + Egypt: 'EG', + El_Salvador: 'SV', + Equatorial_Guinea: 'GQ', + Eritrea: 'ER', + Estonia: 'EE', + Eswatini: 'SZ', + Ethiopia: 'ET', + Fiji: 'FJ', + Finland: 'FI', + France: 'FR', + Gabon: 'GA', + Gambia: 'GM', + Georgia: 'GE', + Germany: 'DE', + Ghana: 'GH', + Greece: 'GR', + Grenada: 'GD', + Guatemala: 'GT', + Guinea: 'GN', + Guinea_Bissau: 'GW', + Guyana: 'GY', + Haiti: 'HT', + Honduras: 'HN', + Hungary: 'HU', + Iceland: 'IS', + India: 'IN', + Indonesia: 'ID', + Iran: 'IR', + Iraq: 'IQ', + Ireland: 'IE', + Israel: 'IL', + Italy: 'IT', + Ivory_Coast: 'CI', + Jamaica: 'JM', + Japan: 'JP', + Jordan: 'JO', + Kazakhstan: 'KZ', + Kenya: 'KE', + Kiribati: 'KI', + North_Korea: 'KP', + South_Korea: 'KR', + Kosovo: 'XK', + Kuwait: 'KW', + Kyrgyzstan: 'KG', + Laos: 'LA', + Latvia: 'LV', + Lebanon: 'LB', + Lesotho: 'LS', + Liberia: 'LR', + Libya: 'LY', + Liechtenstein: 'LI', + Lithuania: 'LT', + Luxembourg: 'LU', + Madagascar: 'MG', + Malawi: 'MW', + Malaysia: 'MY', + Maldives: 'MV', + Mali: 'ML', + Malta: 'MT', + Marshall_Islands: 'MH', + Mauritania: 'MR', + Mauritius: 'MU', + Mexico: 'MX', + Micronesia: 'FM', + Moldova: 'MD', + Monaco: 'MC', + Mongolia: 'MN', + Montenegro: 'ME', + Morocco: 'MA', + Mozambique: 'MZ', + Myanmar: 'MM', + Namibia: 'NA', + Nauru: 'NR', + Nepal: 'NP', + Netherlands: 'NL', + New_Zealand: 'NZ', + Nicaragua: 'NI', + Niger: 'NE', + Nigeria: 'NG', + North_Macedonia: 'MK', + Norway: 'NO', + Oman: 'OM', + Pakistan: 'PK', + Palau: 'PW', + Panama: 'PA', + Papua_New_Guinea: 'PG', + Paraguay: 'PY', + Peru: 'PE', + Philippines: 'PH', + Poland: 'PL', + Portugal: 'PT', + Qatar: 'QA', + Romania: 'RO', + Russia: 'RU', + Rwanda: 'RW', + Saint_Kitts_and_Nevis: 'KN', + Saint_Lucia: 'LC', + Saint_Vincent_and_the_Grenadines: 'VC', + Samoa: 'WS', + San_Marino: 'SM', + Sao_Tome_and_Principe: 'ST', + Saudi_Arabia: 'SA', + Senegal: 'SN', + Serbia: 'RS', + Seychelles: 'SC', + Sierra_Leone: 'SL', + Singapore: 'SG', + Slovakia: 'SK', + Slovenia: 'SI', + Solomon_Islands: 'SB', + Somalia: 'SO', + South_Africa: 'ZA', + Spain: 'ES', + Sri_Lanka: 'LK', + Sudan: 'SD', + Suriname: 'SR', + Sweden: 'SE', + Switzerland: 'CH', + Syria: 'SY', + Taiwan: 'TW', + Tajikistan: 'TJ', + Tanzania: 'TZ', + Thailand: 'TH', + Togo: 'TG', + Tonga: 'TO', + Trinidad_and_Tobago: 'TT', + Tunisia: 'TN', + Turkey: 'TR', + Turkmenistan: 'TM', + Tuvalu: 'TV', + Uganda: 'UG', + Ukraine: 'UA', + United_Arab_Emirates: 'AE', + United_Kingdom: 'GB', + United_States: 'US', + Uruguay: 'UY', + Uzbekistan: 'UZ', + Vanuatu: 'VU', + Vatican_City: 'VA', + Venezuela: 'VE', + Vietnam: 'VN', + Yemen: 'YE', + Zambia: 'ZM', + Zimbabwe: 'ZW', +}; + +// Create an inverse mapping +export const CODE_TO_COUNTRY: { [key: string]: string } = Object.entries( + COUNTRY_CODES, +).reduce((acc, [key, value]) => { + acc[value] = key; + return acc; +}, {} as { [key: string]: string }); + +export function getCountryCode(countryName: string): string | null { + return COUNTRY_CODES[countryName] || null; +} + +export function getCountryName(countryCode: string): string | null { + return CODE_TO_COUNTRY[countryCode] || null; +} diff --git a/packages/api/src/crm/@lib/@utils/index.ts b/packages/api/src/crm/@lib/@utils/index.ts index 1943dfc91..82816b355 100644 --- a/packages/api/src/crm/@lib/@utils/index.ts +++ b/packages/api/src/crm/@lib/@utils/index.ts @@ -62,16 +62,18 @@ export class Utils { } normalizeAddresses(addresses: Address[]) { - const normalizedAddresses = addresses.map((addy) => ({ - ...addy, - created_at: new Date(), - modified_at: new Date(), - id_crm_address: uuidv4(), - owner_type: addy.owner_type ? addy.owner_type : '', - address_type: addy.address_type === '' ? 'primary' : addy.address_type, - })); - - return normalizedAddresses; + if (addresses) { + const normalizedAddresses = addresses.map((addy) => ({ + ...addy, + created_at: new Date(), + modified_at: new Date(), + id_crm_address: uuidv4(), + owner_type: addy.owner_type ? addy.owner_type : '', + address_type: addy.address_type === '' ? 'primary' : addy.address_type, + })); + return normalizedAddresses; + } + return []; } async getRemoteIdFromUserUuid(uuid: string) { diff --git a/packages/api/src/crm/company/services/pipedrive/index.ts b/packages/api/src/crm/company/services/pipedrive/index.ts index 0589828f4..7f531da54 100644 --- a/packages/api/src/crm/company/services/pipedrive/index.ts +++ b/packages/api/src/crm/company/services/pipedrive/index.ts @@ -1,15 +1,14 @@ -import { Injectable } from '@nestjs/common'; -import { ICompanyService } from '@crm/company/types'; -import { CrmObject } from '@crm/@lib/@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 { LoggerService } from '@@core/@core-services/logger/logger.service'; +import { PrismaService } from '@@core/@core-services/prisma/prisma.service'; import { ApiResponse } from '@@core/utils/types'; +import { SyncParam } from '@@core/utils/types/interface'; +import { CrmObject } from '@crm/@lib/@types'; +import { ICompanyService } from '@crm/company/types'; +import { Injectable } from '@nestjs/common'; +import axios from 'axios'; import { ServiceRegistry } from '../registry.service'; import { PipedriveCompanyInput, PipedriveCompanyOutput } from './types'; -import { SyncParam } from '@@core/utils/types/interface'; @Injectable() export class PipedriveService implements ICompanyService { diff --git a/packages/api/src/crm/contact/contact.module.ts b/packages/api/src/crm/contact/contact.module.ts index 93ea088b7..f0d10b3de 100644 --- a/packages/api/src/crm/contact/contact.module.ts +++ b/packages/api/src/crm/contact/contact.module.ts @@ -1,8 +1,6 @@ -import { EncryptionService } from '@@core/@core-services/encryption/encryption.service'; -import { LoggerService } from '@@core/@core-services/logger/logger.service'; import { BullQueueModule } from '@@core/@core-services/queues/queue.module'; +import { IngestDataService } from '@@core/@core-services/unification/ingest-data.service'; import { WebhookService } from '@@core/@core-services/webhooks/panora-webhooks/webhook.service'; -import { ConnectionUtils } from '@@core/connections/@utils'; import { FieldMappingService } from '@@core/field-mapping/field-mapping.service'; import { Utils } from '@crm/@lib/@utils'; import { Module } from '@nestjs/common'; @@ -22,24 +20,18 @@ import { ZendeskContactMapper } from './services/zendesk/mappers'; import { ZohoService } from './services/zoho'; import { ZohoContactMapper } from './services/zoho/mappers'; import { SyncService } from './sync/sync.service'; -import { CoreUnification } from '@@core/@core-services/unification/core-unification.service'; -import { IngestDataService } from '@@core/@core-services/unification/ingest-data.service'; @Module({ imports: [BullQueueModule], controllers: [ContactController], providers: [ ContactService, - FieldMappingService, SyncService, WebhookService, - ServiceRegistry, Utils, - IngestDataService, - /* PROVIDERS SERVICES */ AttioService, ZendeskService, diff --git a/packages/api/src/crm/contact/services/attio/mappers.ts b/packages/api/src/crm/contact/services/attio/mappers.ts index 7d33ef719..03f01481a 100644 --- a/packages/api/src/crm/contact/services/attio/mappers.ts +++ b/packages/api/src/crm/contact/services/attio/mappers.ts @@ -1,4 +1,3 @@ -import { Address } from '@crm/@lib/@types'; import { UnifiedContactInput, UnifiedContactOutput, @@ -8,6 +7,7 @@ import { AttioContactInput, AttioContactOutput } from './types'; import { Utils } from '@crm/@lib/@utils'; import { Injectable } from '@nestjs/common'; import { MappersRegistry } from '@@core/@core-services/registries/mappers.registry'; +import { getCountryCode, getCountryName } from '@@core/utils/types'; @Injectable() export class AttioContactMapper implements IContactMapper { @@ -46,6 +46,30 @@ export class AttioContactMapper implements IContactMapper { result.values.phone_numbers = [primaryPhone]; } + if (source.addresses && source.addresses.length > 0) { + const addy: any = { + line_2: null, + line_3: null, + line_4: null, + latitude: null, + longitude: null, + }; + addy.line_1 = source.addresses[0].street_1; + if (source.addresses[0].city) { + addy.locality = source.addresses[0].city; + } + if (source.addresses[0].state) { + addy.region = source.addresses[0].state; + } + if (source.addresses[0].country) { + addy.country_code = getCountryCode(source.addresses[0].country); + } + if (source.addresses[0].postal_code) { + addy.postcode = source.addresses[0].postal_code; + } + result.values.primary_location = [addy]; + } + if (customFieldMappings && source.field_mappings) { for (const [k, v] of Object.entries(source.field_mappings)) { const mapping = customFieldMappings.find( @@ -101,29 +125,47 @@ export class AttioContactMapper implements IContactMapper { field_mappings[mapping.slug] = contact.values[mapping.remote_id]; } } - const address: Address = { - street_1: null, - city: null, - state: null, - postal_code: null, - country: null, + + const opts: any = { + addresses: [], }; + const address: any = {}; + const addy_exists = + contact.values.primary_location && contact.values.primary_location[0]; + + if (addy_exists && addy_exists.line_1) { + address.street_1 = addy_exists.line_1; + } + if (addy_exists && addy_exists.locality) { + address.city = addy_exists.locality; + } + if (addy_exists && addy_exists.region) { + address.state = addy_exists.region; + } + if (addy_exists && addy_exists.postcode) { + address.postal_code = addy_exists.postcode; + } + if (addy_exists && addy_exists.country_code) { + address.country = getCountryName(addy_exists.country_code); + } + address.address_type = 'PERSONAL'; + opts.addresses[0] = address; + return { remote_id: contact.id.record_id, first_name: contact.values.name[0]?.first_name, last_name: contact.values.name[0]?.last_name, - // user_id: contact.values.created_by[0]?.referenced_actor_id, email_addresses: contact.values.email_addresses?.map((e) => ({ email_address: e.email_address, - email_address_type: e.attribute_type ? e.attribute_type : null, - })), // Map each email + email_address_type: 'PERSONAL', + })), phone_numbers: contact.values.phone_numbers?.map((p) => ({ phone_number: p.original_phone_number, - phone_type: p.attribute_type ? p.attribute_type : null, - })), // Map each phone number, + phone_type: 'MOBILE', + })), field_mappings, - addresses: [address], + ...opts, }; } } diff --git a/packages/api/src/crm/contact/services/close/mappers.ts b/packages/api/src/crm/contact/services/close/mappers.ts index 4919592bb..d9f143170 100644 --- a/packages/api/src/crm/contact/services/close/mappers.ts +++ b/packages/api/src/crm/contact/services/close/mappers.ts @@ -26,8 +26,7 @@ export class CloseContactMapper implements IContactMapper { remote_id: string; }[], ): Promise { - // Assuming 'email_addresses' array contains at least one email and 'phone_numbers' array contains at least one phone number - const result: CloseContactInput = { + /*const result: CloseContactInput = { name: `${source.first_name ?? null} ${source.last_name ?? null}`, phones: source?.phone_numbers?.map( ({ phone_number, phone_type }) => @@ -57,8 +56,8 @@ export class CloseContactMapper implements IContactMapper { } } } - - return result; + */ + return; } async unify( diff --git a/packages/api/src/crm/contact/services/contact.service.ts b/packages/api/src/crm/contact/services/contact.service.ts index aa1b06ad7..11319244c 100644 --- a/packages/api/src/crm/contact/services/contact.service.ts +++ b/packages/api/src/crm/contact/services/contact.service.ts @@ -166,7 +166,6 @@ export class ContactService { const normalizedAddresses = this.utils.normalizeAddresses( contact.addresses, ); - const data: any = { first_name: contact.first_name, last_name: contact.last_name, @@ -234,6 +233,7 @@ export class ContactService { data: { ...email, id_crm_contact: contactId, + id_connection: connectionId, }, }); } @@ -257,6 +257,7 @@ export class ContactService { data: { ...phone, id_crm_contact: contactId, + id_connection: connectionId, }, }); } @@ -303,6 +304,7 @@ export class ContactService { data: { ...email, id_crm_contact: contactId, + id_connection: connectionId, }, }), ), @@ -316,6 +318,7 @@ export class ContactService { data: { ...phone, id_crm_contact: contactId, + id_connection: connectionId, }, }), ), @@ -559,8 +562,11 @@ export class ContactService { ressource_owner_id: contact.id, }, }); - const remote_data = JSON.parse(resp.data); - return { ...contact, remote_data }; + if (resp && resp.data) { + const remote_data = JSON.parse(resp.data); + return { ...contact, remote_data }; + } + return contact; }), ); diff --git a/packages/api/src/crm/contact/services/hubspot/mappers.ts b/packages/api/src/crm/contact/services/hubspot/mappers.ts index 37e12f56a..6a98961c1 100644 --- a/packages/api/src/crm/contact/services/hubspot/mappers.ts +++ b/packages/api/src/crm/contact/services/hubspot/mappers.ts @@ -21,7 +21,6 @@ export class HubspotContactMapper implements IContactMapper { remote_id: string; }[], ): Promise { - // Assuming 'email_addresses' array contains at least one email and 'phone_numbers' array contains at least one phone number const primaryEmail = source.email_addresses?.[0]?.email_address; const primaryPhone = source.phone_numbers?.[0]?.phone_number; @@ -37,6 +36,22 @@ export class HubspotContactMapper implements IContactMapper { result.phone = primaryPhone; } + if (source.addresses && source.addresses.length > 0) { + result.address = source.addresses[0].street_1; + if (source.addresses[0].city) { + result.city = source.addresses[0].city; + } + if (source.addresses[0].state) { + result.state = source.addresses[0].state; + } + if (source.addresses[0].country) { + result.country = source.addresses[0].country; + } + if (source.addresses[0].postal_code) { + result.zip = source.addresses[0].postal_code; + } + } + if (source.user_id) { const owner_id = await this.utils.getRemoteIdFromUserUuid(source.user_id); if (owner_id) { @@ -98,13 +113,27 @@ export class HubspotContactMapper implements IContactMapper { } } - /*todo: const address: Address = { - street_1: null, - city: null, - state: null, - postal_code: null, - country: null, - };*/ + const opts: any = { + addresses: [], + }; + + const address: any = {}; + if (contact.properties.address) { + address.street_1 = contact.properties.address; + } + if (contact.properties.city) { + address.city = contact.properties.city; + } + if (contact.properties.state) { + address.state = contact.properties.state; + } + if (contact.properties.zip) { + address.postal_code = contact.properties.zip; + } + if (contact.properties.country) { + address.country = contact.properties.country; + } + opts.addresses[0] = address; return { remote_id: contact.id, @@ -124,8 +153,8 @@ export class HubspotContactMapper implements IContactMapper { owner_type: 'contact', }, ], + ...opts, field_mappings, - addresses: [], }; } } diff --git a/packages/api/src/crm/contact/services/hubspot/types.ts b/packages/api/src/crm/contact/services/hubspot/types.ts index 424dee920..2dcf215c2 100644 --- a/packages/api/src/crm/contact/services/hubspot/types.ts +++ b/packages/api/src/crm/contact/services/hubspot/types.ts @@ -25,12 +25,20 @@ export interface HubspotContactOutput { } type HubspotPropertiesOuput = { - createdate: string; - email: string; - firstname: string; - hs_object_id: string; - lastmodifieddate: string; - lastname: string; + email?: string; + firstname?: string; + phone?: string; + lastname?: string; + city?: string; + country?: string; + zip?: string; + state?: string; + address?: string; + mobilephone?: string; + hubspot_owner_id?: string; + associatedcompanyid?: string; + fax?: string; + jobtitle?: string; [key: string]: string; }; @@ -42,4 +50,10 @@ export const commonHubspotProperties = { lastmodifieddate: '', lastname: '', hubspot_owner_id: '', + country: '', + zip: '', + state: '', + address: '', + city: '', + mobilephone: '', }; diff --git a/packages/api/src/crm/contact/services/pipedrive/mappers.ts b/packages/api/src/crm/contact/services/pipedrive/mappers.ts index affc4d1d8..d94d985a4 100644 --- a/packages/api/src/crm/contact/services/pipedrive/mappers.ts +++ b/packages/api/src/crm/contact/services/pipedrive/mappers.ts @@ -22,7 +22,6 @@ export class PipedriveContactMapper implements IContactMapper { remote_id: string; }[], ): Promise { - // Assuming 'email_addresses' and 'phone_numbers' arrays contain at least one entry const primaryEmail = source.email_addresses?.[0]?.email_address; const primaryPhone = source.phone_numbers?.[0]?.phone_number; @@ -30,13 +29,21 @@ export class PipedriveContactMapper implements IContactMapper { ? [{ value: primaryEmail, primary: true, label: null }] : []; const phoneObject = primaryPhone - ? [{ value: primaryPhone, primary: true, label: null }] + ? [ + { + value: primaryPhone, + primary: true, + label: + source.phone_numbers?.[0]?.phone_type == 'MOBILE' + ? 'Mobile' + : null, + }, + ] : []; const result: PipedriveContactInput = { name: `${source.first_name} ${source.last_name}`, email: emailObject, phone: phoneObject, - cc_email: source.user_id, //TODO: i put in here for tmp reasons uuid }; if (source.user_id) { @@ -109,13 +116,6 @@ export class PipedriveContactMapper implements IContactMapper { field_mappings[mapping.slug] = contact[mapping.remote_id]; } } - const address: Address = { - street_1: null, - city: null, - state: null, - postal_code: null, - country: null, - }; let opts: any = {}; if (contact.owner_id.id) { const user_id = await this.utils.getUserUuidFromRemoteId( @@ -135,14 +135,13 @@ export class PipedriveContactMapper implements IContactMapper { last_name: contact.last_name, email_addresses: contact.email.map((e) => ({ email_address: e.value, - email_address_type: e.label ? e.label : null, + email_address_type: e.label ? e.label.toUpperCase() : null, })), // Map each email phone_numbers: contact.phone.map((p) => ({ phone_number: p.value, - phone_type: p.label ? p.label : null, + phone_type: p.label ? p.label.toUpperCase() : null, })), // Map each phone number, field_mappings, - addresses: [address], ...opts, }; } diff --git a/packages/api/src/crm/contact/services/zoho/index.ts b/packages/api/src/crm/contact/services/zoho/index.ts index f915e0ce1..b427fabc1 100644 --- a/packages/api/src/crm/contact/services/zoho/index.ts +++ b/packages/api/src/crm/contact/services/zoho/index.ts @@ -49,9 +49,20 @@ export class ZohoService implements IContactService { }, }, ); - //this.logger.log('zoho resp is ' + JSON.stringify(resp)); + + const final_res = await axios.get( + `${connection.account_url}/Contacts/${resp.data.data[0].details.id}`, + { + headers: { + 'Content-Type': 'application/json', + Authorization: `Zoho-oauthtoken ${this.cryptoService.decrypt( + connection.access_token, + )}`, + }, + }, + ); return { - data: resp.data.data, + data: final_res.data.data[0], message: 'Zoho contact created', statusCode: 201, }; diff --git a/packages/api/src/crm/contact/services/zoho/mappers.ts b/packages/api/src/crm/contact/services/zoho/mappers.ts index ce3da81e6..21263dcce 100644 --- a/packages/api/src/crm/contact/services/zoho/mappers.ts +++ b/packages/api/src/crm/contact/services/zoho/mappers.ts @@ -32,9 +32,12 @@ export class ZohoContactMapper implements IContactMapper { if (primaryEmail) { result.Email = primaryEmail; } - if (primaryPhone) { + if (primaryPhone && source.phone_numbers?.[0]?.phone_type == 'WORK') { result.Phone = primaryPhone; } + if (primaryPhone && source.phone_numbers?.[0]?.phone_type == 'MOBILE') { + result.Mobile = primaryPhone; + } if (source.addresses && source.addresses[0]) { result.Mailing_Street = source.addresses[0].street_1; result.Mailing_City = source.addresses[0].city; @@ -101,7 +104,7 @@ export class ZohoContactMapper implements IContactMapper { // Constructing email and phone details const email_addresses = contact && contact.Email - ? [{ email_address: contact.Email, email_address_type: 'primary' }] + ? [{ email_address: contact.Email, email_address_type: 'PERSONAL' }] : []; const phone_numbers = []; @@ -109,13 +112,13 @@ export class ZohoContactMapper implements IContactMapper { if (contact && contact.Phone) { phone_numbers.push({ phone_number: contact.Phone, - phone_type: 'work', + phone_type: 'WORK', }); } if (contact && contact.Mobile) { phone_numbers.push({ phone_number: contact.Mobile, - phone_type: 'mobile', + phone_type: 'MOBILE', }); } if (contact && contact.Fax) { @@ -149,8 +152,8 @@ export class ZohoContactMapper implements IContactMapper { return { remote_id: String(contact.id), - first_name: contact.First_Name ? contact.First_Name : null, - last_name: contact.Last_Name ? contact.Last_Name : null, + first_name: contact.First_Name ?? null, + last_name: contact.Last_Name ?? null, email_addresses, phone_numbers, field_mappings, diff --git a/packages/api/src/crm/engagement/services/pipedrive/index.ts b/packages/api/src/crm/engagement/services/pipedrive/index.ts index e3b39ecd4..eb8e8cb72 100644 --- a/packages/api/src/crm/engagement/services/pipedrive/index.ts +++ b/packages/api/src/crm/engagement/services/pipedrive/index.ts @@ -24,11 +24,10 @@ export class PipedriveService implements IEngagementService { this.registry.registerService('pipedrive', this); } - /*TODO: */ async addEngagement( engagementData: PipedriveEngagementInput, linkedUserId: string, - engagement_type: string, + engagement_type?: string, ): Promise> { try { const connection = await this.prisma.connections.findFirst({ @@ -38,12 +37,23 @@ export class PipedriveService implements IEngagementService { vertical: 'crm', }, }); - return; - /*return { + const resp = await axios.post( + `${connection.account_url}/activities`, + JSON.stringify(engagementData), + { + headers: { + 'Content-Type': 'application/json', + Authorization: `Bearer ${this.cryptoService.decrypt( + connection.access_token, + )}`, + }, + }, + ); + return { data: resp.data.data, message: 'Pipedrive engagement created', statusCode: 201, - };*/ + }; } catch (error) { throw error; } diff --git a/packages/api/src/crm/engagement/services/pipedrive/mappers.ts b/packages/api/src/crm/engagement/services/pipedrive/mappers.ts index 84bbe2d85..255ef2af2 100644 --- a/packages/api/src/crm/engagement/services/pipedrive/mappers.ts +++ b/packages/api/src/crm/engagement/services/pipedrive/mappers.ts @@ -19,15 +19,57 @@ export class PipedriveEngagementMapper implements IEngagementMapper { ); } - //TODO: - desunify( + async desunify( source: UnifiedEngagementInput, customFieldMappings?: { slug: string; remote_id: string; }[], - ): PipedriveEngagementInput { - return; + ): Promise { + const result: PipedriveEngagementInput = { + subject: source.subject || null, + public_description: source.content || null, + }; + + if (source.type) { + result.type = `${source.type.substring(0, 1)}${source.type + .substring(1) + .toUpperCase()}`; + } + + if (source.start_at && source.end_time) { + const startDate = new Date(source.start_at); + const endDate = new Date(source.end_time); + + const diffMilliseconds = endDate.getTime() - startDate.getTime(); + const durationInSeconds = Math.round(diffMilliseconds / 1000); + + const dueDate = startDate.toISOString().split('T')[0]; + const dueTime = startDate.toTimeString().split(' ')[0].substring(0, 5); + const duration = `${String(Math.floor(durationInSeconds / 60)).padStart( + 2, + '0', + )}:${String(durationInSeconds % 60).padStart(2, '0')}`; + + result.due_date = dueDate; + result.due_time = dueTime; + result.duration = duration; + } + + if (source.user_id) { + const owner_id = await this.utils.getRemoteIdFromUserUuid(source.user_id); + if (owner_id) { + result.user_id = Number(owner_id); + } + } + if (source.company_id) { + const id = await this.utils.getRemoteIdFromCompanyUuid(source.company_id); + if (id) { + result.org_id = Number(id); + } + } + + return result; } async unify( @@ -177,7 +219,35 @@ export class PipedriveEngagementMapper implements IEngagementMapper { connectionId, ); if (person_id) { - opts.contacts = [person_id]; + opts.contacts.push(person_id); + } + } + + if (engagement.attendee && engagement.attendee.length > 0) { + for (const p of engagement.attendee) { + if (p.person_id) { + const id = await this.utils.getContactUuidFromRemoteId( + String(p.person_id), + connectionId, + ); + if (id) { + opts.contacts.push(id); + } + } + } + } + + if (engagement.participants && engagement.participants.length > 0) { + for (const p of engagement.participants) { + if (p.person_id) { + const id = await this.utils.getContactUuidFromRemoteId( + String(p.person_id), + connectionId, + ); + if (id) { + opts.contacts.push(id); + } + } } } @@ -236,7 +306,35 @@ export class PipedriveEngagementMapper implements IEngagementMapper { connectionId, ); if (person_id) { - opts.contacts = [person_id]; + opts.contacts.push(person_id); + } + } + + if (engagement.attendee && engagement.attendee.length > 0) { + for (const p of engagement.attendee) { + if (p.person_id) { + const id = await this.utils.getContactUuidFromRemoteId( + String(p.person_id), + connectionId, + ); + if (id) { + opts.contacts.push(id); + } + } + } + } + + if (engagement.participants && engagement.participants.length > 0) { + for (const p of engagement.participants) { + if (p.person_id) { + const id = await this.utils.getContactUuidFromRemoteId( + String(p.person_id), + connectionId, + ); + if (id) { + opts.contacts.push(id); + } + } } } @@ -294,7 +392,35 @@ export class PipedriveEngagementMapper implements IEngagementMapper { connectionId, ); if (person_id) { - opts.contacts = [person_id]; + opts.contacts.push(person_id); + } + } + + if (engagement.attendee && engagement.attendee.length > 0) { + for (const p of engagement.attendee) { + if (p.person_id) { + const id = await this.utils.getContactUuidFromRemoteId( + String(p.person_id), + connectionId, + ); + if (id) { + opts.contacts.push(id); + } + } + } + } + + if (engagement.participants && engagement.participants.length > 0) { + for (const p of engagement.participants) { + if (p.person_id) { + const id = await this.utils.getContactUuidFromRemoteId( + String(p.person_id), + connectionId, + ); + if (id) { + opts.contacts.push(id); + } + } } } diff --git a/packages/api/src/crm/engagement/services/zoho/index.ts b/packages/api/src/crm/engagement/services/zoho/index.ts index 81131f0a0..6745033e9 100644 --- a/packages/api/src/crm/engagement/services/zoho/index.ts +++ b/packages/api/src/crm/engagement/services/zoho/index.ts @@ -1,15 +1,14 @@ -import { Injectable } from '@nestjs/common'; -import { IEngagementService } from '@crm/engagement/types'; -import { CrmObject } from '@crm/@lib/@types'; -import { ZohoEngagementInput, ZohoEngagementOutput } from './types'; -import axios from 'axios'; +import { EncryptionService } from '@@core/@core-services/encryption/encryption.service'; import { LoggerService } from '@@core/@core-services/logger/logger.service'; import { PrismaService } from '@@core/@core-services/prisma/prisma.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'; +import { CrmObject } from '@crm/@lib/@types'; +import { IEngagementService } from '@crm/engagement/types'; +import { Injectable } from '@nestjs/common'; +import axios from 'axios'; +import { ServiceRegistry } from '../registry.service'; +import { ZohoEngagementOutput } from './types'; @Injectable() export class ZohoService implements IEngagementService { @@ -24,41 +23,6 @@ export class ZohoService implements IEngagementService { ); this.registry.registerService('zoho', this); } - //ONLY CALLS FOR ZOHO - async addEngagement( - engagementData: ZohoEngagementInput, - linkedUserId: string, - ): Promise> { - try { - const connection = await this.prisma.connections.findFirst({ - where: { - id_linked_user: linkedUserId, - provider_slug: 'zoho', - vertical: 'crm', - }, - }); - const resp = await axios.post( - `${connection.account_url}/Campaigns`, - { data: [engagementData] }, - { - headers: { - 'Content-Type': 'application/json', - Authorization: `Zoho-oauthtoken ${this.cryptoService.decrypt( - connection.access_token, - )}`, - }, - }, - ); - //this.logger.log('zoho resp is ' + JSON.stringify(resp)); - return { - data: resp.data.data, - message: 'Zoho engagement created', - statusCode: 201, - }; - } catch (error) { - throw error; - } - } private async syncCalls(linkedUserId: string) { try { diff --git a/packages/api/src/crm/engagement/types/index.ts b/packages/api/src/crm/engagement/types/index.ts index f7a1749b7..65c2a5cb0 100644 --- a/packages/api/src/crm/engagement/types/index.ts +++ b/packages/api/src/crm/engagement/types/index.ts @@ -8,7 +8,7 @@ import { ApiResponse } from '@@core/utils/types'; import { IBaseObjectService, SyncParam } from '@@core/utils/types/interface'; export interface IEngagementService extends IBaseObjectService { - addEngagement( + addEngagement?( engagementData: DesunifyReturnType, linkedUserId: string, engagement_type: string, diff --git a/packages/api/src/crm/task/services/zoho/index.ts b/packages/api/src/crm/task/services/zoho/index.ts index 4f7413509..2f6b1c453 100644 --- a/packages/api/src/crm/task/services/zoho/index.ts +++ b/packages/api/src/crm/task/services/zoho/index.ts @@ -1,15 +1,14 @@ -import { Injectable } from '@nestjs/common'; -import { ITaskService } from '@crm/task/types'; -import { CrmObject } from '@crm/@lib/@types'; -import { ZohoTaskInput, ZohoTaskOutput } from './types'; -import axios from 'axios'; +import { EncryptionService } from '@@core/@core-services/encryption/encryption.service'; import { LoggerService } from '@@core/@core-services/logger/logger.service'; import { PrismaService } from '@@core/@core-services/prisma/prisma.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'; +import { CrmObject } from '@crm/@lib/@types'; +import { ITaskService } from '@crm/task/types'; +import { Injectable } from '@nestjs/common'; +import axios from 'axios'; +import { ServiceRegistry } from '../registry.service'; +import { ZohoTaskInput, ZohoTaskOutput } from './types'; @Injectable() export class ZohoService implements ITaskService { @@ -49,7 +48,6 @@ export class ZohoService implements ITaskService { }, }, ); - //this.logger.log('zoho resp is ' + JSON.stringify(resp)); return { data: resp.data.data, message: 'Zoho task created',