Skip to content

Commit

Permalink
netsuite connector page (#561)
Browse files Browse the repository at this point in the history
  • Loading branch information
NileshPant1999 authored Mar 14, 2024
1 parent fcb268b commit f761220
Show file tree
Hide file tree
Showing 34 changed files with 758 additions and 13 deletions.
8 changes: 8 additions & 0 deletions src/app/branding/branding-config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,7 @@ const kbArticles: KbArticle = {
topLevelArticles: {
BAMBOO_HR: `${brandingConfig.helpArticleDomain}/en/articles/6845034-fyle-bamboo-hr-integration`,
QBD: `${brandingConfig.helpArticleDomain}/en/collections/215867-integrations-with-fyle#quickbooks-desktop`,
NETSUITE: `${brandingConfig.helpArticleDomain}/en/articles/4424242-fyle-netsuite-integration`,
QBO: `${brandingConfig.helpArticleDomain}/en/articles/6208620-how-to-set-up-the-fyle-quickbooks-online-integration`,
INTACCT: `${brandingConfig.helpArticleDomain}/en/collections/215867-integrations-with-fyle`,
TRAVELPERK: `${brandingConfig.helpArticleDomain}/en/articles/7549535-how-are-travelperk-invoices-created-as-expenses-in-fyle`,
Expand All @@ -101,6 +102,9 @@ const kbArticles: KbArticle = {
CONNECTOR: `${brandingConfig.helpArticleDomain}/en/articles/8394683-how-to-configure-the-fyle-sage-intacct-integration#h_38e0c9bea6`,
SKIP_EXPORT: `${brandingConfig.helpArticleDomain}/en/articles/7882821-how-to-skip-exporting-specific-expenses-from-fyle-to-sage-intacct`
},
NETSUITE: {
CONNECTOR: `${brandingConfig.helpArticleDomain}/en/articles/8394683-how-to-configure-the-fyle-sage-intacct-integration#h_85f929716c`
},
// TODO: Update KB articles for Sage 300
SAGE300: {
IMPORT_SETTING: `${brandingConfig.helpArticleDomain}/en/articles/8394683-how-to-configure-the-fyle-sage-intacct-integration#h_85f929716c`,
Expand Down Expand Up @@ -138,6 +142,7 @@ const kbArticles: KbArticle = {
QBD: `${brandingConfig.helpArticleDomain}/en/collections/215867-integrations-with-fyle#quickbooks-desktop`,
QBO: `${brandingConfig.helpArticleDomain}/en/articles/9054778-configure-capital-one-quickbooks-online-integration`,
INTACCT: `${brandingConfig.helpArticleDomain}/en/collections/215867-integrations-with-fyle`,
NETSUITE: `${brandingConfig.helpArticleDomain}/en/articles/4424242-fyle-netsuite-integration`,
TRAVELPERK: `${brandingConfig.helpArticleDomain}/en/articles/7549535-how-are-travelperk-invoices-created-as-expenses-in-fyle`,
SAGE300: `${brandingConfig.helpArticleDomain}/en/collections/215867-integrations-with-fyle`,
BUSINESS_CENTRAL: `${brandingConfig.helpArticleDomain}/en/collections/215867-integrations-with-fyle`
Expand All @@ -151,6 +156,9 @@ const kbArticles: KbArticle = {
CONNECTOR: `${brandingConfig.helpArticleDomain}/en/articles/8394683-how-to-configure-the-fyle-sage-intacct-integration#h_38e0c9bea6`,
SKIP_EXPORT: `${brandingConfig.helpArticleDomain}/en/articles/7882821-how-to-skip-exporting-specific-expenses-from-fyle-to-sage-intacct`
},
NETSUITE: {
CONNECTOR: `${brandingConfig.helpArticleDomain}/en/articles/8394683-how-to-configure-the-fyle-sage-intacct-integration#h_85f929716c`
},
SAGE300: {
IMPORT_SETTING: `${brandingConfig.helpArticleDomain}/en/articles/8394683-how-to-configure-the-fyle-sage-intacct-integration#h_85f929716c`,
EXPORT_SETTING: `${brandingConfig.helpArticleDomain}/en/articles/8394683-how-to-configure-the-fyle-sage-intacct-integration#h_6492c5038d`,
Expand Down
4 changes: 4 additions & 0 deletions src/app/core/models/branding/kb-article.model.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ export type KbArticle = {
QBD: string;
QBO: string;
INTACCT: string;
NETSUITE: string;
TRAVELPERK: string;
SAGE300: string;
BUSINESS_CENTRAL: string;
Expand All @@ -18,6 +19,9 @@ export type KbArticle = {
CONNECTOR: string;
SKIP_EXPORT: string;
},
NETSUITE: {
CONNECTOR: string;
}
SAGE300: {
IMPORT_SETTING: string;
EXPORT_SETTING: string;
Expand Down
1 change: 1 addition & 0 deletions src/app/core/models/enum/enum.model.ts
Original file line number Diff line number Diff line change
Expand Up @@ -179,6 +179,7 @@ export enum XeroOnboardingState {

export enum NetsuiteOnboardingState {
CONNECTION = 'CONNECTION',
SUBSIDIARY = 'SUBSDIARY',
EXPORT_SETTINGS = 'EXPORT_SETTINGS',
IMPORT_SETTINGS = 'IMPORT_SETTINGS',
ADVANCED_CONFIGURATION = 'ADVANCED_CONFIGURATION',
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import { DestinationAttribute } from "../../db/destination-attribute.model";

type NetsuiteDestinationAttributeDetail = {
country?: string;
};

export interface NetsuiteDestinationAttribute extends DestinationAttribute {
detail: NetsuiteDestinationAttributeDetail;
}
11 changes: 11 additions & 0 deletions src/app/core/models/netsuite/db/netsuite-credentials.model.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
export type NetsuiteCredential = {
id: number;
ns_account_id: string;
ns_consumer_key: string;
ns_consumer_secret: string;
ns_token_id: string;
ns_token_secret: string;
created_at: Date;
updated_at: Date;
workspace: number;
}
25 changes: 25 additions & 0 deletions src/app/core/models/netsuite/db/subsidiary-mapping.model.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import { NetsuiteDestinationAttribute } from "./destination-attribute.model";
import { NetsuiteSubsidiaryMappingPost } from "../netsuite-configuration/netsuite-connector.model";

export type SubsidiaryMapping = {
id?: number;
subsidiary_name: string;
country_name: string | null;
internal_id: string;
created_at?: Date;
updated_at?: Date;
workspace: number;
};


export class NetsuiteSubsidiaryMappingModel {
static constructPayload(netsuiteSubsidiaryId: any, subsidiaries: NetsuiteDestinationAttribute[], workspaceId: number): NetsuiteSubsidiaryMappingPost {
const filteredSubsidiary = subsidiaries.filter(entity => entity.destination_id === netsuiteSubsidiaryId);
return {
subsidiary_name: filteredSubsidiary[0].value,
internal_id: filteredSubsidiary[0].destination_id,
country_name: filteredSubsidiary[0].detail?.country ? filteredSubsidiary[0].detail.country : null,
workspace: workspaceId
};
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import { FormGroup } from "@angular/forms";
import { environment } from "src/environments/environment";

export type NetsuiteConnector = {
ns_account_id: string;
}

export interface NetsuiteConnectorPost extends NetsuiteConnector {
ns_token_id: string;
ns_token_secret: string;
}


export class NetsuiteConnectorModel {
static constructPayload(form: FormGroup): NetsuiteConnectorPost {
return {
ns_account_id: form.value.accountId,
ns_token_id: form.value.tokenId,
ns_token_secret: form.value.tokenSecret
};
}
}


export type NetsuiteSubsidiaryMappingPost = {
country_name: string | null,
internal_id: string,
subsidiary_name: string,
workspace: number
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
import { brandingFeatureConfig } from "src/app/branding/branding-config";
import { NetsuiteOnboardingState } from "../../enum/enum.model";
import { OnboardingStepper } from "../../misc/onboarding-stepper.model";


type NetsuiteOnboardingStepperMap = {
[NetsuiteOnboardingState.CONNECTION]: number,
[NetsuiteOnboardingState.SUBSIDIARY]: number,
[NetsuiteOnboardingState.EXPORT_SETTINGS]: number,
[NetsuiteOnboardingState.IMPORT_SETTINGS]: number,
[NetsuiteOnboardingState.ADVANCED_CONFIGURATION]: number,
[NetsuiteOnboardingState.COMPLETE]: number,
}

export class NetsuiteOnboardingModel {
private onboardingSteps: OnboardingStepper[] = [
{
active: false,
completed: false,
step: 'Connect to NetSuite',
icon: 'link-vertical-medium',
route: '/integrations/netsuite/onboarding/connector',
styleClasses: ['step-name-connector--text']
},
{
active: false,
completed: false,
step: 'Export Settings',
icon: 'arrow-tail-up-medium',
route: '/integrations/netsuite/onboarding/export_settings',
styleClasses: ['step-name-export--text']
},
{
active: false,
completed: false,
step: 'Import Settings',
icon: 'arrow-tail-down-medium',
route: '/integrations/netsuite/onboarding/import_settings',
styleClasses: ['step-name-export--text']
},
{
active: false,
completed: false,
step: 'Advanced Settings',
icon: 'gear-medium',
route: '/integrations/netsuite/onboarding/advanced_settings',
styleClasses: ['step-name-advanced--text']
}
];

private readonly onboardingStateStepMap: NetsuiteOnboardingStepperMap = {
[NetsuiteOnboardingState.CONNECTION]: 1,
[NetsuiteOnboardingState.SUBSIDIARY]: 2,
[NetsuiteOnboardingState.EXPORT_SETTINGS]: 3,
[NetsuiteOnboardingState.IMPORT_SETTINGS]: 4,
[NetsuiteOnboardingState.ADVANCED_CONFIGURATION]: 5,
[NetsuiteOnboardingState.COMPLETE]: 6
};

getOnboardingSteps(currentStep: string, onboardingState: NetsuiteOnboardingState): OnboardingStepper[] {
this.onboardingSteps.forEach(step => {
if (step.step.toLowerCase() === currentStep.toLowerCase()) {
step.active = true;
} else {
step.active = false;
}
});

for (let index = this.onboardingStateStepMap[onboardingState] - 2; index > 0; index--) {
this.onboardingSteps[index - 1].completed = true;
}

if (!brandingFeatureConfig.featureFlags.mapEmployees) {
this.onboardingSteps.splice(1, 1);
}

return this.onboardingSteps;
}
}
5 changes: 3 additions & 2 deletions src/app/core/services/integration/tracking.service.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { Injectable } from '@angular/core';
import { BusinessCentralOnboardingState, BusinessCentralUpdateEvent, ClickEvent, IntacctOnboardingState, IntacctUpdateEvent, Page, QBDOnboardingState, Sage300OnboardingState, Sage300UpdateEvent, TrackingApp, TravelPerkOnboardingState, TravelperkUpdateEvent, UpdateEvent } from '../../models/enum/enum.model';
import { BusinessCentralOnboardingState, BusinessCentralUpdateEvent, ClickEvent, IntacctOnboardingState, IntacctUpdateEvent, NetsuiteOnboardingState, Page, QBDOnboardingState, Sage300OnboardingState, Sage300UpdateEvent, TrackingApp, TravelPerkOnboardingState, TravelperkUpdateEvent, UpdateEvent } from '../../models/enum/enum.model';
import { MappingAlphabeticalFilterAdditionalProperty, ResolveMappingErrorProperty, UpdateEventAdditionalProperty, UpdateIntacctEventAdditionalProperty } from '../../models/misc/tracking.model';
import { QBDAdvancedSettingsPost } from '../../models/qbd/qbd-configuration/advanced-setting.model';
import { QBDExportSettingPost } from '../../models/qbd/qbd-configuration/export-setting.model';
Expand All @@ -14,6 +14,7 @@ import { Sage300AdvancedSettingPost } from '../../models/sage300/sage300-configu
import { BusinessCentralExportSettingPost } from '../../models/business-central/business-central-configuration/business-central-export-setting.model';
import { BusinessCentralImportSettingsPost } from '../../models/business-central/business-central-configuration/business-central-import-settings.model';
import { BusinessCentralAdvancedSettingsPost } from '../../models/business-central/business-central-configuration/business-central-advanced-settings.model';
import { NetsuiteSubsidiaryMappingPost } from '../../models/netsuite/netsuite-configuration/netsuite-connector.model';
import { TravelperkPaymentProfileSettingPost } from '../../models/travelperk/travelperk-configuration/travelperk-payment-profile-settings.model';
import { TravelperkAdvancedSettingPost } from '../../models/travelperk/travelperk-configuration/travelperk-advanced-settings.model';

Expand Down Expand Up @@ -90,7 +91,7 @@ export class TrackingService {
this.eventTrack(`Step ${stepNumber} completed: ${eventName}`, trackingApp, additionalProperties);
}

integrationsOnboardingCompletion(trackingApp: TrackingApp, eventName: IntacctOnboardingState, stepNumber: number, additionalProperties: LocationEntityPost | ExportSettingPost | ImportSettingPost | AdvancedSettingsPost | void): void {
integrationsOnboardingCompletion(trackingApp: TrackingApp, eventName: IntacctOnboardingState | NetsuiteOnboardingState, stepNumber: number, additionalProperties: LocationEntityPost | ExportSettingPost | ImportSettingPost | AdvancedSettingsPost | NetsuiteSubsidiaryMappingPost | void): void {
this.eventTrack(`Step ${stepNumber} completed: ${eventName}`, trackingApp, additionalProperties);
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import { TestBed } from '@angular/core/testing';

import { NetsuiteConnectorService } from './netsuite-connector.service';

describe('NetsuiteConnectorService', () => {
let service: NetsuiteConnectorService;

beforeEach(() => {
TestBed.configureTestingModule({});
service = TestBed.inject(NetsuiteConnectorService);
});

it('should be created', () => {
expect(service).toBeTruthy();
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
import { Injectable } from '@angular/core';
import { CacheBuster, Cacheable, globalCacheBusterNotifier } from 'ts-cacheable';
import { ApiService } from '../../common/api.service';
import { StorageService } from '../../common/storage.service';
import { NetsuiteCredential } from 'src/app/core/models/netsuite/db/netsuite-credentials.model';
import { SubsidiaryMapping } from 'src/app/core/models/netsuite/db/subsidiary-mapping.model';
import { Observable, Subject } from 'rxjs';
import { NetsuiteConnectorPost, NetsuiteSubsidiaryMappingPost } from 'src/app/core/models/netsuite/netsuite-configuration/netsuite-connector.model';
import { WorkspaceService } from '../../common/workspace.service';


const netsuiteCredentialCache = new Subject<void>();


@Injectable({
providedIn: 'root'
})
export class NetsuiteConnectorService {

workspaceId: number;

constructor(
private apiService: ApiService,
private workspaceService: WorkspaceService,
private storageService: StorageService
) { }

@Cacheable({
cacheBusterObserver: netsuiteCredentialCache
})
getNetsuiteCredentials(): Observable<NetsuiteCredential> {
this.workspaceId = this.storageService.get('workspaceId');
return this.apiService.get(`/workspaces/${this.workspaceId}/credentials/netsuite/`, {});
}

@CacheBuster({
cacheBusterNotifier: netsuiteCredentialCache
})
connectNetsuite(data: NetsuiteConnectorPost): Observable<NetsuiteCredential> {
this.workspaceId = this.storageService.get('workspaceId');
globalCacheBusterNotifier.next();
return this.apiService.post(`/workspaces/${this.workspaceId}/credentials/netsuite/`, data);
}

postSubsdiaryMapping(subsdiaryMappingPayload: NetsuiteSubsidiaryMappingPost): Observable<SubsidiaryMapping> {
const workspaceId = this.workspaceService.getWorkspaceId();

return this.apiService.post(`/workspaces/${workspaceId}/mappings/subsidiaries/`, subsdiaryMappingPayload);
}

getSubsidiaryMapping(): Observable<SubsidiaryMapping> {
const workspaceId = this.workspaceService.getWorkspaceId();
return this.apiService.get(
`/workspaces/${workspaceId}/mappings/subsidiaries/`, {}
);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import { TestBed } from '@angular/core/testing';

import { NetsuiteMappingsService } from './netsuite-mappings.service';

describe('NetsuiteMappingsService', () => {
let service: NetsuiteMappingsService;

beforeEach(() => {
TestBed.configureTestingModule({});
service = TestBed.inject(NetsuiteMappingsService);
});

it('should be created', () => {
expect(service).toBeTruthy();
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import { Injectable } from '@angular/core';
import { ApiService } from '../../common/api.service';
import { Observable } from 'rxjs';
import { WorkspaceService } from '../../common/workspace.service';

@Injectable({
providedIn: 'root'
})
export class NetsuiteMappingsService {

constructor(
private apiService: ApiService,
private workspaceService: WorkspaceService
) { }

refreshNetsuiteDimensions(dimensionsToSync: string[] = []) {
const workspaceId = this.workspaceService.getWorkspaceId();

return this.apiService.post(`/workspaces/${workspaceId}/netsuite/refresh_dimensions/`, {
dimensions_to_sync: dimensionsToSync
});
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import { TestBed } from '@angular/core/testing';

import { NetsuiteWorkspaceService } from './netsuite-workspace.service';

describe('NetsuiteWorkspaceService', () => {
let service: NetsuiteWorkspaceService;

beforeEach(() => {
TestBed.configureTestingModule({});
service = TestBed.inject(NetsuiteWorkspaceService);
});

it('should be created', () => {
expect(service).toBeTruthy();
});
});
Loading

0 comments on commit f761220

Please sign in to comment.