Skip to content

Commit

Permalink
✨ Deel + sage hris integrations
Browse files Browse the repository at this point in the history
  • Loading branch information
naelob committed Aug 16, 2024
1 parent 6dcb65f commit 2e62b82
Show file tree
Hide file tree
Showing 37 changed files with 1,846 additions and 20 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import { NamelyConnectionService } from './services/namely/namely.service';
import { PayfitConnectionService } from './services/payfit/payfit.service';
import { ServiceRegistry } from './services/registry.service';
import { RipplingConnectionService } from './services/rippling/rippling.service';
import { SageConnectionService } from './services/sage/sage.service';

@Module({
imports: [WebhookModule, BullQueueModule],
Expand All @@ -30,6 +31,7 @@ import { RipplingConnectionService } from './services/rippling/rippling.service'
FactorialConnectionService,
NamelyConnectionService,
BamboohrConnectionService,
SageConnectionService,
],
exports: [HrisConnectionsService],
})
Expand Down
143 changes: 143 additions & 0 deletions packages/api/src/@core/connections/hris/services/sage/sage.service.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,143 @@
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 { RetryHandler } from '@@core/@core-services/request-retry/retry.handler';
import { ConnectionsStrategiesService } from '@@core/connections-strategies/connections-strategies.service';
import { ConnectionUtils } from '@@core/connections/@utils';
import {
AbstractBaseConnectionService,
OAuthCallbackParams,
PassthroughInput,
RefreshParams,
} from '@@core/connections/@utils/types';
import { PassthroughResponse } from '@@core/passthrough/types';
import { Injectable } from '@nestjs/common';
import {
AuthStrategy,
CONNECTORS_METADATA,
DynamicApiUrl,
providerToType,
} from '@panora/shared';
import { v4 as uuidv4 } from 'uuid';
import { ServiceRegistry } from '../registry.service';

@Injectable()
export class SageConnectionService extends AbstractBaseConnectionService {
private readonly type: string;

constructor(
protected prisma: PrismaService,
private logger: LoggerService,
protected cryptoService: EncryptionService,
private registry: ServiceRegistry,
private connectionUtils: ConnectionUtils,
private cService: ConnectionsStrategiesService,
private retryService: RetryHandler,
) {
super(prisma, cryptoService);
this.logger.setContext(SageConnectionService.name);
this.registry.registerService('sage', this);
this.type = providerToType('sage', 'hris', AuthStrategy.oauth2);
}

async passthrough(
input: PassthroughInput,
connectionId: string,
): Promise<PassthroughResponse> {
try {
const { headers } = input;
const config = await this.constructPassthrough(input, connectionId);

const connection = await this.prisma.connections.findUnique({
where: {
id_connection: connectionId,
},
});

config.headers = {
...config.headers,
...headers,
'X-Auth-Token': this.cryptoService.decrypt(connection.access_token),
};

return await this.retryService.makeRequest(
{
method: config.method,
url: config.url,
data: config.data,
headers: config.headers,
},
'hris.sage.passthrough',
config.linkedUserId,
);
} catch (error) {
throw error;
}
}

async handleCallback(opts: OAuthCallbackParams) {
try {
const { linkedUserId, projectId, body } = opts;
const { api_key, subdomain } = body;
const isNotUnique = await this.prisma.connections.findFirst({
where: {
id_linked_user: linkedUserId,
provider_slug: 'sage',
vertical: 'hris',
},
});

let db_res;
const connection_token = uuidv4();
const BASE_API_URL = (
CONNECTORS_METADATA['hris']['sage'].urls.apiUrl as DynamicApiUrl
)(subdomain);

if (isNotUnique) {
db_res = await this.prisma.connections.update({
where: {
id_connection: isNotUnique.id_connection,
},
data: {
access_token: this.cryptoService.encrypt(api_key),
account_url: BASE_API_URL,
status: 'valid',
created_at: new Date(),
},
});
} else {
db_res = await this.prisma.connections.create({
data: {
id_connection: uuidv4(),
connection_token: connection_token,
provider_slug: 'sage',
vertical: 'hris',
token_type: 'basic',
account_url: BASE_API_URL,
access_token: this.cryptoService.encrypt(api_key),
status: 'valid',
created_at: new Date(),
projects: {
connect: { id_project: projectId },
},
linked_users: {
connect: {
id_linked_user: await this.connectionUtils.getLinkedUserId(
projectId,
linkedUserId,
),
},
},
},
});
}
return db_res;
} catch (error) {
throw error;
}
}

handleTokenRefresh?(opts: RefreshParams): Promise<any> {
throw new Error('Method not implemented.');
}
}
31 changes: 24 additions & 7 deletions packages/api/src/@core/utils/types/original/original.hris.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,21 @@
/* INPUT */

import { GustoBenefitOutput } from '@hris/benefit/services/gusto/types';
import { DeelCompanyOutput } from '@hris/company/services/deel/types';
import { GustoCompanyOutput } from '@hris/company/services/gusto/types';
import { DeelEmployeeOutput } from '@hris/employee/services/deel/types';
import { GustoEmployeeOutput } from '@hris/employee/services/gusto/types';
import { SageEmployeeOutput } from '@hris/employee/services/sage/types';
import { GustoEmployerbenefitOutput } from '@hris/employerbenefit/services/gusto/types';
import { DeelEmploymentOutput } from '@hris/employment/services/deel/types';
import { GustoEmploymentOutput } from '@hris/employment/services/gusto/types';
import { DeelGroupOutput } from '@hris/group/services/deel/types';
import { GustoGroupOutput } from '@hris/group/services/gusto/types';
import { SageGroupOutput } from '@hris/group/services/sage/types';
import { DeelLocationOutput } from '@hris/location/services/deel/types';
import { GustoLocationOutput } from '@hris/location/services/gusto/types';
import { SageTimeoffOutput } from '@hris/timeoff/services/sage/types';
import { SageTimeoffbalanceOutput } from '@hris/timeoffbalance/services/sage/types';

/* bankinfo */
export type OriginalBankInfoInput = any;
Expand Down Expand Up @@ -79,13 +88,16 @@ export type OriginalBankInfoOutput = any;
export type OriginalBenefitOutput = GustoBenefitOutput;

/* company */
export type OriginalCompanyOutput = GustoCompanyOutput;
export type OriginalCompanyOutput = GustoCompanyOutput | DeelCompanyOutput;

/* dependent */
export type OriginalDependentOutput = any;

/* employee */
export type OriginalEmployeeOutput = GustoEmployeeOutput;
export type OriginalEmployeeOutput =
| GustoEmployeeOutput
| SageEmployeeOutput
| DeelEmployeeOutput;

/* employeepayrollrun */
export type OriginalEmployeePayrollRunOutput = any;
Expand All @@ -94,13 +106,18 @@ export type OriginalEmployeePayrollRunOutput = any;
export type OriginalEmployerBenefitOutput = GustoEmployerbenefitOutput;

/* employment */
export type OriginalEmploymentOutput = GustoEmploymentOutput;
export type OriginalEmploymentOutput =
| GustoEmploymentOutput
| DeelEmploymentOutput;

/* group */
export type OriginalGroupOutput = GustoGroupOutput;
export type OriginalGroupOutput =
| GustoGroupOutput
| DeelGroupOutput
| SageGroupOutput;

/* location */
export type OriginalLocationOutput = GustoLocationOutput;
export type OriginalLocationOutput = GustoLocationOutput | DeelLocationOutput;

/* paygroup */
export type OriginalPayGroupOutput = any;
Expand All @@ -109,10 +126,10 @@ export type OriginalPayGroupOutput = any;
export type OriginalPayrollRunOutput = any;

/* timeoff */
export type OriginalTimeoffOutput = any;
export type OriginalTimeoffOutput = SageTimeoffOutput;

/* timeoffbalance */
export type OriginalTimeoffBalanceOutput = any;
export type OriginalTimeoffBalanceOutput = SageTimeoffbalanceOutput;

/* timesheetentry */
export type OriginalTimesheetentryOutput = any;
Expand Down
15 changes: 15 additions & 0 deletions packages/api/src/hris/@lib/@utils/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,21 @@ export class Utils {
}
}

async getGroupUuidFromRemoteId(id: string, connection_id: string) {
try {
const res = await this.prisma.hris_groups.findFirst({
where: {
remote_id: id,
id_connection: connection_id,
},
});
if (!res) return;
return res.id_hris_group;
} catch (error) {
throw error;
}
}

async getEmployerBenefitUuidFromRemoteId(id: string, connection_id: string) {
try {
const res = await this.prisma.hris_employer_benefits.findFirst({
Expand Down
4 changes: 4 additions & 0 deletions packages/api/src/hris/company/company.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ import { CoreUnification } from '@@core/@core-services/unification/core-unificat
import { GustoCompanyMapper } from './services/gusto/mappers';
import { GustoService } from './services/gusto';
import { Utils } from '@hris/@lib/@utils';
import { DeelService } from './services/deel';
import { DeelCompanyMapper } from './services/deel/mappers';
@Module({
controllers: [CompanyController],
providers: [
Expand All @@ -20,8 +22,10 @@ import { Utils } from '@hris/@lib/@utils';
ServiceRegistry,
IngestDataService,
GustoCompanyMapper,
DeelCompanyMapper,
/* PROVIDERS SERVICES */
GustoService,
DeelService,
],
exports: [SyncService],
})
Expand Down
72 changes: 72 additions & 0 deletions packages/api/src/hris/company/services/deel/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
import { EncryptionService } from '@@core/@core-services/encryption/encryption.service';
import { EnvironmentService } from '@@core/@core-services/environment/environment.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 { HrisObject } from '@hris/@lib/@types';
import { ICompanyService } from '@hris/company/types';
import { Injectable } from '@nestjs/common';
import axios from 'axios';
import { ServiceRegistry } from '../registry.service';
import { DeelCompanyOutput } from './types';
import { DesunifyReturnType } from '@@core/utils/types/desunify.input';
import { OriginalCompanyOutput } from '@@core/utils/types/original/original.hris';

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

addCompany(
companyData: DesunifyReturnType,
linkedUserId: string,
): Promise<ApiResponse<OriginalCompanyOutput>> {
throw new Error('Method not implemented.');
}

async sync(data: SyncParam): Promise<ApiResponse<DeelCompanyOutput[]>> {
try {
const { linkedUserId } = data;

const connection = await this.prisma.connections.findFirst({
where: {
id_linked_user: linkedUserId,
provider_slug: 'deel',
vertical: 'hris',
},
});

const resp = await axios.get(
`${connection.account_url}/rest/v2/legal-entities`,
{
headers: {
'Content-Type': 'application/json',
Authorization: `Bearer ${this.cryptoService.decrypt(
connection.access_token,
)}`,
},
},
);
this.logger.log(`Synced deel companys !`);

return {
data: resp.data.data,
message: 'Deel companys retrieved',
statusCode: 200,
};
} catch (error) {
throw error;
}
}
}
Loading

0 comments on commit 2e62b82

Please sign in to comment.