From 4530baddd91dc6eafbaf84e294e99f14f9080fa6 Mon Sep 17 00:00:00 2001 From: Fyle Date: Fri, 8 Mar 2024 13:02:17 +0530 Subject: [PATCH 1/8] Onboarding landing construction --- src/app/branding/branding-config.ts | 32 +- .../core/models/branding/demo-video.model.ts | 1 + .../core/models/branding/kb-article.model.ts | 12 +- src/app/core/models/enum/enum.model.ts | 3 +- src/app/core/models/misc/tracking.model.ts | 6 +- .../xero-onboarding-landing.component.html | 18 +- .../xero-onboarding-landing.component.ts | 91 +++++- .../xero-onboarding/xero-onboarding.module.ts | 4 +- src/app/integrations/xero/xero.module.ts | 6 +- .../app-landing-page-header.component.html | 2 +- .../app-landing-page-header.component.ts | 2 +- .../flow-charts/fyle-xero-data-flow.svg | 278 ++++++++++++++++++ 12 files changed, 439 insertions(+), 16 deletions(-) create mode 100644 src/assets/flow-charts/fyle-xero-data-flow.svg diff --git a/src/app/branding/branding-config.ts b/src/app/branding/branding-config.ts index 85f40ab91..62c20a641 100644 --- a/src/app/branding/branding-config.ts +++ b/src/app/branding/branding-config.ts @@ -90,7 +90,8 @@ const kbArticles: KbArticle = { INTACCT: `${brandingConfig.helpArticleDomain}/en/collections/215867-integrations-with-fyle`, 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/articles/8911018-how-to-configure-the-fyle-dynamics-365-business-central-integration` + BUSINESS_CENTRAL: `${brandingConfig.helpArticleDomain}/en/articles/8911018-how-to-configure-the-fyle-dynamics-365-business-central-integration`, + XERO: `${brandingConfig.helpArticleDomain}/en/articles/8911018-how-to-configure-the-fyle-dynamics-365-business-central-integration` }, onboardingArticles: { INTACCT: { @@ -129,6 +130,16 @@ const kbArticles: KbArticle = { PAYMENT_PROFILE_SETTINGS: 'https://help.fylehq.com/en/articles/7193187-how-to-set-up-the-fyle-travelperk-integration#h_0f8ebdfa10', ADVANCED_SETTING: 'https://help.fylehq.com/en/articles/7193187-how-to-set-up-the-fyle-travelperk-integration#h_281acb3026', LANDING: 'https://help.fylehq.com/en/articles/7193187-how-to-set-up-the-fyle-travelperk-integration' + }, + // TODO + XERO: { + LANDING: `${brandingConfig.helpArticleDomain}/en/articles/6208620-how-to-set-up-the-fyle-quickbooks-online-integration`, + CONNECTOR: `${brandingConfig.helpArticleDomain}/en/articles/6208620-how-to-set-up-the-fyle-quickbooks-online-integration-v2-0`, + EMPLOYEE_SETTING: `${brandingConfig.helpArticleDomain}/en/articles/6208620-how-to-set-up-the-fyle-quickbooks-online-integration-v2-0#h_d70f1d54cc`, + EXPORT_SETTING: `${brandingConfig.helpArticleDomain}/en/articles/6208620-how-to-set-up-the-fyle-quickbooks-online-integration-v2-0#h_dca1353686`, + IMPORT_SETTING: `${brandingConfig.helpArticleDomain}/en/articles/6208620-how-to-set-up-the-fyle-quickbooks-online-integration-v2-0#h_b8a2df129f`, + ADVANCED_SETTING: `${brandingConfig.helpArticleDomain}/en/articles/6208620-how-to-set-up-the-fyle-quickbooks-online-integration-v2-0#h_18c28de6c7`, + SKIP_EXPORT: `${brandingConfig.helpArticleDomain}/en/articles/7044785-how-to-skip-exporting-specific-expenses-from-fyle-to-quickbooks-online` } } }, @@ -140,7 +151,8 @@ const kbArticles: KbArticle = { INTACCT: `${brandingConfig.helpArticleDomain}/en/collections/215867-integrations-with-fyle`, 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` + BUSINESS_CENTRAL: `${brandingConfig.helpArticleDomain}/en/collections/215867-integrations-with-fyle`, + XERO: `${brandingConfig.helpArticleDomain}/en/collections/215867-integrations-with-fyle` }, onboardingArticles: { INTACCT: { @@ -178,6 +190,16 @@ const kbArticles: KbArticle = { PAYMENT_PROFILE_SETTINGS: 'https://help.fylehq.com/en/articles/7193187-how-to-set-up-the-fyle-travelperk-integration#h_0f8ebdfa10', ADVANCED_SETTING: 'https://help.fylehq.com/en/articles/7193187-how-to-set-up-the-fyle-travelperk-integration#h_281acb3026', LANDING: 'https://help.fylehq.com/en/articles/7193187-how-to-set-up-the-fyle-travelperk-integration' + }, + // TODO + XERO: { + LANDING: `${brandingConfig.helpArticleDomain}/en/articles/6208620-how-to-set-up-the-fyle-quickbooks-online-integration`, + CONNECTOR: `${brandingConfig.helpArticleDomain}/en/articles/6208620-how-to-set-up-the-fyle-quickbooks-online-integration-v2-0`, + EMPLOYEE_SETTING: `${brandingConfig.helpArticleDomain}/en/articles/6208620-how-to-set-up-the-fyle-quickbooks-online-integration-v2-0#h_d70f1d54cc`, + EXPORT_SETTING: `${brandingConfig.helpArticleDomain}/en/articles/6208620-how-to-set-up-the-fyle-quickbooks-online-integration-v2-0#h_dca1353686`, + IMPORT_SETTING: `${brandingConfig.helpArticleDomain}/en/articles/6208620-how-to-set-up-the-fyle-quickbooks-online-integration-v2-0#h_b8a2df129f`, + ADVANCED_SETTING: `${brandingConfig.helpArticleDomain}/en/articles/6208620-how-to-set-up-the-fyle-quickbooks-online-integration-v2-0#h_18c28de6c7`, + SKIP_EXPORT: `${brandingConfig.helpArticleDomain}/en/articles/7044785-how-to-skip-exporting-specific-expenses-from-fyle-to-quickbooks-online` } } } @@ -195,7 +217,8 @@ const demoVideoLinks: DemoVideo = { QBO: 'https://www.youtube.com/embed/b63lS2DG5j4', // TODO: Update link for MS Dynamics BUSINESS_CENTRAL: 'https://www.youtube.com/embed/2oYdc8KcQnk', - TRAVELPERK: 'https://www.youtube.com/embed/2oYdc8KcQnk' + TRAVELPERK: 'https://www.youtube.com/embed/2oYdc8KcQnk', + XERO: 'https://www.youtube.com/embed/2oYdc8KcQnk' } }, co: { @@ -206,7 +229,8 @@ const demoVideoLinks: DemoVideo = { QBO: 'https://www.youtube.com/embed/b63lS2DG5j4', // TODO: Update link for MS Dynamics BUSINESS_CENTRAL: 'https://www.youtube.com/embed/2oYdc8KcQnk', - TRAVELPERK: 'https://www.youtube.com/embed/2oYdc8KcQnk' + TRAVELPERK: 'https://www.youtube.com/embed/2oYdc8KcQnk', + XERO: 'https://www.youtube.com/embed/2oYdc8KcQnk' } } }; diff --git a/src/app/core/models/branding/demo-video.model.ts b/src/app/core/models/branding/demo-video.model.ts index 700e3f9db..3225b7503 100644 --- a/src/app/core/models/branding/demo-video.model.ts +++ b/src/app/core/models/branding/demo-video.model.ts @@ -6,6 +6,7 @@ export type DemoVideo = { QBO: string; BUSINESS_CENTRAL: string; TRAVELPERK: string; + XERO:string; } } }; diff --git a/src/app/core/models/branding/kb-article.model.ts b/src/app/core/models/branding/kb-article.model.ts index 2c58a5e4f..43791be9c 100644 --- a/src/app/core/models/branding/kb-article.model.ts +++ b/src/app/core/models/branding/kb-article.model.ts @@ -8,6 +8,7 @@ export type KbArticle = { TRAVELPERK: string; SAGE300: string; BUSINESS_CENTRAL: string; + XERO: string; }, onboardingArticles: { INTACCT: { @@ -45,7 +46,16 @@ export type KbArticle = { PAYMENT_PROFILE_SETTINGS: string; ADVANCED_SETTING: string; LANDING: string; - } + }, + XERO: { + LANDING: string; + CONNECTOR: string; + EMPLOYEE_SETTING: string; + EXPORT_SETTING: string; + IMPORT_SETTING: string; + ADVANCED_SETTING: string; + SKIP_EXPORT: string; + }, } } }; diff --git a/src/app/core/models/enum/enum.model.ts b/src/app/core/models/enum/enum.model.ts index 53d3ebd33..54159772a 100644 --- a/src/app/core/models/enum/enum.model.ts +++ b/src/app/core/models/enum/enum.model.ts @@ -82,7 +82,8 @@ export enum AppName { SAGE300 = 'Sage 300 CRE', QBO = 'QuickBooks Online', BUSINESS_CENTRAL = 'Dynamics 365 Business Central', - NETSUITE = 'NetSuite' + NETSUITE = 'NetSuite', + XERO = 'Xero' } export enum AppNameInService { diff --git a/src/app/core/models/misc/tracking.model.ts b/src/app/core/models/misc/tracking.model.ts index 13307bdf0..826928d16 100644 --- a/src/app/core/models/misc/tracking.model.ts +++ b/src/app/core/models/misc/tracking.model.ts @@ -51,7 +51,8 @@ export type TrackingAppMap = { [AppName.INTACCT]: TrackingApp.INTACCT, [AppName.QBO]: TrackingApp.QBO, [AppName.TRAVELPERK]: TrackingApp.TRAVELPERK, - [AppName.NETSUITE]: TrackingApp.NETSUITE + [AppName.NETSUITE]: TrackingApp.NETSUITE, + [AppName.XERO]: TrackingApp.XERO } export const trackingAppMap: TrackingAppMap = { @@ -62,5 +63,6 @@ export const trackingAppMap: TrackingAppMap = { [AppName.INTACCT]: TrackingApp.INTACCT, [AppName.QBO]: TrackingApp.QBO, [AppName.TRAVELPERK]: TrackingApp.TRAVELPERK, - [AppName.NETSUITE]: TrackingApp.NETSUITE + [AppName.NETSUITE]: TrackingApp.NETSUITE, + [AppName.XERO]: TrackingApp.XERO }; diff --git a/src/app/integrations/xero/xero-onboarding/xero-onboarding-landing/xero-onboarding-landing.component.html b/src/app/integrations/xero/xero-onboarding/xero-onboarding-landing/xero-onboarding-landing.component.html index 6d618195e..e24f9b45b 100644 --- a/src/app/integrations/xero/xero-onboarding/xero-onboarding-landing/xero-onboarding-landing.component.html +++ b/src/app/integrations/xero/xero-onboarding/xero-onboarding-landing/xero-onboarding-landing.component.html @@ -1 +1,17 @@ -

xero-onboarding-landing works!

+
+
+ +
+
+ +
+
+ + + diff --git a/src/app/integrations/xero/xero-onboarding/xero-onboarding-landing/xero-onboarding-landing.component.ts b/src/app/integrations/xero/xero-onboarding/xero-onboarding-landing/xero-onboarding-landing.component.ts index 2cd2b35ac..60a49c328 100644 --- a/src/app/integrations/xero/xero-onboarding/xero-onboarding-landing/xero-onboarding-landing.component.ts +++ b/src/app/integrations/xero/xero-onboarding/xero-onboarding-landing/xero-onboarding-landing.component.ts @@ -1,15 +1,100 @@ -import { Component, OnInit } from '@angular/core'; +import { Component, OnDestroy, OnInit } from '@angular/core'; +import { Router } from '@angular/router'; +import { Subscription } from 'rxjs'; +import { brandingConfig, brandingDemoVideoLinks, brandingKbArticles } from 'src/app/branding/branding-config'; +import { AppName, ToastSeverity, XeroOnboardingState } from 'src/app/core/models/enum/enum.model'; +import { ConfigurationWarningOut } from 'src/app/core/models/misc/configuration-warning.model'; +import { XeroCredentials } from 'src/app/core/models/xero/db/xero-credential.model'; +import { HelperService } from 'src/app/core/services/common/helper.service'; +import { IntegrationsToastService } from 'src/app/core/services/common/integrations-toast.service'; +import { WorkspaceService } from 'src/app/core/services/common/workspace.service'; +import { XeroConnectorService } from 'src/app/core/services/xero/xero-configuration/xero-connector.service'; +import { XeroHelperService } from 'src/app/core/services/xero/xero-core/xero-helper.service'; +import { environment } from 'src/environments/environment'; @Component({ selector: 'app-xero-onboarding-landing', templateUrl: './xero-onboarding-landing.component.html', styleUrls: ['./xero-onboarding-landing.component.scss'] }) -export class XeroOnboardingLandingComponent implements OnInit { +export class XeroOnboardingLandingComponent implements OnInit, OnDestroy { - constructor() { } + isIncorrectXeroConnectedDialogVisible: boolean = false; + + appName: string = AppName.XERO; + + xeroConnectionInProgress: boolean = false; + + isIntegrationConnected: boolean = false; + + redirectLink: string = brandingKbArticles.onboardingArticles.XERO.LANDING; + + embedVideoLink = brandingDemoVideoLinks.onboarding.XERO; + + private oauthCallbackSubscription: Subscription; + + readonly brandingConfig = brandingConfig; + + constructor( + private helperService: HelperService, + private xeroConnectorService: XeroConnectorService, + private workspaceService: WorkspaceService, + private router: Router, + private xeroHelper: XeroHelperService, + private toastService: IntegrationsToastService + ) { } + + acceptWarning(data: ConfigurationWarningOut): void { + this.isIncorrectXeroConnectedDialogVisible = false; + if (data.hasAccepted) { + this.router.navigate([`/integrations/xero/onboarding/landing`]); + } + } + + private postXeroCredentials(code: string): void { + this.xeroConnectorService.connectXero(this.workspaceService.getWorkspaceId(), code).subscribe((xeroCredentials: XeroCredentials) => { + this.isIntegrationConnected = true; + this.xeroConnectionInProgress = false; + this.checkProgressAndRedirect(); + }, (error) => { + const errorMessage = 'message' in error.error ? error.error.message : 'Failed to connect to Xero Tenant. Please try again'; + if (errorMessage === 'Please choose the correct Xero account') { + this.isIntegrationConnected = false; + this.xeroConnectionInProgress = false; + this.isIncorrectXeroConnectedDialogVisible = true; + } else { + this.toastService.displayToastMessage(ToastSeverity.ERROR, errorMessage); + this.router.navigate([`/integrations/xero/onboarding/landing`]); + } + }); + } + + private checkProgressAndRedirect(): void { + const onboardingState: XeroOnboardingState = this.workspaceService.getOnboardingState(); + if (onboardingState !== XeroOnboardingState.COMPLETE) { + this.router.navigate(['integrations/xero/onboarding/connector']); + } else { + this.router.navigate(['integrations/xero/main/dashboard']); + } + } + + connectXero() { + this.xeroConnectionInProgress = true; + const url = `${environment.xero_authorize_uri}?client_id=${environment.xero_oauth_client_id}&scope=${environment.xero_scope}&response_type=code&redirect_uri=${environment.xero_oauth_redirect_uri}&state=qbo_local_redirect`; + this.oauthCallbackSubscription = this.helperService.oauthCallbackUrl.subscribe((callbackURL: string) => { + const code = callbackURL.split('code=')[1]?.split('&')[0]; + this.postXeroCredentials(code); + }); + this.helperService.oauthHandler(url); + } ngOnInit(): void { } + ngOnDestroy(): void { + if (this.oauthCallbackSubscription) { + this.oauthCallbackSubscription.unsubscribe(); + } + } + } diff --git a/src/app/integrations/xero/xero-onboarding/xero-onboarding.module.ts b/src/app/integrations/xero/xero-onboarding/xero-onboarding.module.ts index 0387ba663..82fbd8468 100644 --- a/src/app/integrations/xero/xero-onboarding/xero-onboarding.module.ts +++ b/src/app/integrations/xero/xero-onboarding/xero-onboarding.module.ts @@ -10,6 +10,7 @@ import { XeroOnboardingImportSettingsComponent } from './xero-onboarding-import- import { XeroOnboardingAdvancedSettingsComponent } from './xero-onboarding-advanced-settings/xero-onboarding-advanced-settings.component'; import { XeroOnboardingDoneComponent } from './xero-onboarding-done/xero-onboarding-done.component'; import { XeroSharedModule } from '../xero-shared/xero-shared.module'; +import { SharedModule } from 'src/app/shared/shared.module'; @NgModule({ @@ -25,7 +26,8 @@ import { XeroSharedModule } from '../xero-shared/xero-shared.module'; imports: [ CommonModule, XeroSharedModule, - XeroOnboardingRoutingModule + XeroOnboardingRoutingModule, + SharedModule ] }) export class XeroOnboardingModule { } diff --git a/src/app/integrations/xero/xero.module.ts b/src/app/integrations/xero/xero.module.ts index e43b6d6ac..f4000278f 100644 --- a/src/app/integrations/xero/xero.module.ts +++ b/src/app/integrations/xero/xero.module.ts @@ -2,12 +2,16 @@ import { NgModule } from '@angular/core'; import { CommonModule } from '@angular/common'; import { XeroRoutingModule } from './xero-routing.module'; +import { XeroSharedModule } from './xero-shared/xero-shared.module'; +import { SharedModule } from 'src/app/shared/shared.module'; @NgModule({ declarations: [], imports: [ CommonModule, - XeroRoutingModule + XeroRoutingModule, + XeroSharedModule, + SharedModule ] }) export class XeroModule { } diff --git a/src/app/shared/components/helper/app-landing-page-header/app-landing-page-header.component.html b/src/app/shared/components/helper/app-landing-page-header/app-landing-page-header.component.html index 1d4f1fa42..9f76126d5 100644 --- a/src/app/shared/components/helper/app-landing-page-header/app-landing-page-header.component.html +++ b/src/app/shared/components/helper/app-landing-page-header/app-landing-page-header.component.html @@ -40,7 +40,7 @@ - diff --git a/src/app/shared/components/helper/app-landing-page-header/app-landing-page-header.component.ts b/src/app/shared/components/helper/app-landing-page-header/app-landing-page-header.component.ts index dd59939ab..8b0f25113 100644 --- a/src/app/shared/components/helper/app-landing-page-header/app-landing-page-header.component.ts +++ b/src/app/shared/components/helper/app-landing-page-header/app-landing-page-header.component.ts @@ -87,7 +87,7 @@ export class AppLandingPageHeaderComponent implements OnInit { connect(): void { - if (this.appName === AppName.TRAVELPERK || this.appName === AppName.BUSINESS_CENTRAL || this.appName === AppName.BAMBOO_HR) { + if (this.appName === AppName.TRAVELPERK || this.appName === AppName.BUSINESS_CENTRAL || this.appName === AppName.BAMBOO_HR || this.appName === AppName.XERO) { this.initiateOAuth(); return; } else if (this.postConnectionRoute === 'qbd/onboarding/export_settings') { diff --git a/src/assets/flow-charts/fyle-xero-data-flow.svg b/src/assets/flow-charts/fyle-xero-data-flow.svg new file mode 100644 index 000000000..90a18895f --- /dev/null +++ b/src/assets/flow-charts/fyle-xero-data-flow.svg @@ -0,0 +1,278 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + From 25bc6158ff9220a065baf02a751b9fc5f7423ae8 Mon Sep 17 00:00:00 2001 From: Fyle Date: Fri, 8 Mar 2024 13:03:06 +0530 Subject: [PATCH 2/8] Onboarding landing construction --- .../xero-onboarding-landing.component.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/app/integrations/xero/xero-onboarding/xero-onboarding-landing/xero-onboarding-landing.component.ts b/src/app/integrations/xero/xero-onboarding/xero-onboarding-landing/xero-onboarding-landing.component.ts index 60a49c328..c20157066 100644 --- a/src/app/integrations/xero/xero-onboarding/xero-onboarding-landing/xero-onboarding-landing.component.ts +++ b/src/app/integrations/xero/xero-onboarding/xero-onboarding-landing/xero-onboarding-landing.component.ts @@ -80,7 +80,7 @@ export class XeroOnboardingLandingComponent implements OnInit, OnDestroy { connectXero() { this.xeroConnectionInProgress = true; - const url = `${environment.xero_authorize_uri}?client_id=${environment.xero_oauth_client_id}&scope=${environment.xero_scope}&response_type=code&redirect_uri=${environment.xero_oauth_redirect_uri}&state=qbo_local_redirect`; + const url = `${environment.xero_authorize_uri}?client_id=${environment.xero_oauth_client_id}&scope=${environment.xero_scope}&response_type=code&redirect_uri=${environment.xero_oauth_redirect_uri}&state=xero_local_redirect`; this.oauthCallbackSubscription = this.helperService.oauthCallbackUrl.subscribe((callbackURL: string) => { const code = callbackURL.split('code=')[1]?.split('&')[0]; this.postXeroCredentials(code); From f5875ce24a2c3e8269e6afb997fb5a1555d07336 Mon Sep 17 00:00:00 2001 From: Fyle Date: Fri, 15 Mar 2024 15:21:20 +0530 Subject: [PATCH 3/8] PR comments fix --- src/app/branding/branding-config.ts | 32 +++++++++---------- .../core/models/branding/kb-article.model.ts | 1 - .../xero-onboarding-landing.component.html | 4 +-- .../app-landing-page-header.component.html | 7 +++- .../app-landing-page-header.component.ts | 4 +++ src/assets/buttons/connect-to-xero.svg | 1 + 6 files changed, 28 insertions(+), 21 deletions(-) create mode 100644 src/assets/buttons/connect-to-xero.svg diff --git a/src/app/branding/branding-config.ts b/src/app/branding/branding-config.ts index 447dc7472..3c717bc3b 100644 --- a/src/app/branding/branding-config.ts +++ b/src/app/branding/branding-config.ts @@ -91,7 +91,7 @@ const kbArticles: KbArticle = { 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/articles/8911018-how-to-configure-the-fyle-dynamics-365-business-central-integration`, - XERO: `${brandingConfig.helpArticleDomain}/en/articles/8911018-how-to-configure-the-fyle-dynamics-365-business-central-integration` + XERO: `${brandingConfig.helpArticleDomain}/en/collections/215867-integrations-with-fyle#xero-2-0` }, onboardingArticles: { INTACCT: { @@ -133,13 +133,12 @@ const kbArticles: KbArticle = { }, // TODO XERO: { - LANDING: `${brandingConfig.helpArticleDomain}/en/articles/6208620-how-to-set-up-the-fyle-quickbooks-online-integration`, - CONNECTOR: `${brandingConfig.helpArticleDomain}/en/articles/6208620-how-to-set-up-the-fyle-quickbooks-online-integration-v2-0`, - EMPLOYEE_SETTING: `${brandingConfig.helpArticleDomain}/en/articles/6208620-how-to-set-up-the-fyle-quickbooks-online-integration-v2-0#h_d70f1d54cc`, - EXPORT_SETTING: `${brandingConfig.helpArticleDomain}/en/articles/6208620-how-to-set-up-the-fyle-quickbooks-online-integration-v2-0#h_dca1353686`, - IMPORT_SETTING: `${brandingConfig.helpArticleDomain}/en/articles/6208620-how-to-set-up-the-fyle-quickbooks-online-integration-v2-0#h_b8a2df129f`, - ADVANCED_SETTING: `${brandingConfig.helpArticleDomain}/en/articles/6208620-how-to-set-up-the-fyle-quickbooks-online-integration-v2-0#h_18c28de6c7`, - SKIP_EXPORT: `${brandingConfig.helpArticleDomain}/en/articles/7044785-how-to-skip-exporting-specific-expenses-from-fyle-to-quickbooks-online` + LANDING: `${brandingConfig.helpArticleDomain}/en/articles/6721333-how-to-set-up-the-fyle-xero-integration`, + CONNECTOR: `${brandingConfig.helpArticleDomain}/en/articles/6721333-how-to-set-up-the-fyle-xero-integration-v2-0#h_e3ade308dc`, + EMPLOYEE_SETTING: `${brandingConfig.helpArticleDomain}/en/articles/6208620-how-to-set-up-the-fyle-xero-integration-v2-0#h_d70f1d54cc`, + EXPORT_SETTING: `${brandingConfig.helpArticleDomain}/en/articles/6721333-how-to-set-up-the-fyle-xero-integration-v2-0#h_ad07470d98`, + IMPORT_SETTING: `${brandingConfig.helpArticleDomain}/en/articles/6721333-how-to-set-up-the-fyle-xero-integration-v2-0#h_04d289fd42`, + ADVANCED_SETTING: `${brandingConfig.helpArticleDomain}/en/articles/6721333-how-to-set-up-the-fyle-xero-integration-v2-0#h_d95b791edd` } } }, @@ -193,13 +192,12 @@ const kbArticles: KbArticle = { }, // TODO XERO: { - LANDING: `${brandingConfig.helpArticleDomain}/en/articles/6208620-how-to-set-up-the-fyle-quickbooks-online-integration`, - CONNECTOR: `${brandingConfig.helpArticleDomain}/en/articles/6208620-how-to-set-up-the-fyle-quickbooks-online-integration-v2-0`, - EMPLOYEE_SETTING: `${brandingConfig.helpArticleDomain}/en/articles/6208620-how-to-set-up-the-fyle-quickbooks-online-integration-v2-0#h_d70f1d54cc`, - EXPORT_SETTING: `${brandingConfig.helpArticleDomain}/en/articles/6208620-how-to-set-up-the-fyle-quickbooks-online-integration-v2-0#h_dca1353686`, - IMPORT_SETTING: `${brandingConfig.helpArticleDomain}/en/articles/6208620-how-to-set-up-the-fyle-quickbooks-online-integration-v2-0#h_b8a2df129f`, - ADVANCED_SETTING: `${brandingConfig.helpArticleDomain}/en/articles/6208620-how-to-set-up-the-fyle-quickbooks-online-integration-v2-0#h_18c28de6c7`, - SKIP_EXPORT: `${brandingConfig.helpArticleDomain}/en/articles/7044785-how-to-skip-exporting-specific-expenses-from-fyle-to-quickbooks-online` + LANDING: `${brandingConfig.helpArticleDomain}/en/articles/6721333-how-to-set-up-the-fyle-xero-integration`, + CONNECTOR: `${brandingConfig.helpArticleDomain}/en/articles/6721333-how-to-set-up-the-fyle-xero-integration-v2-0#h_e3ade308dc`, + EMPLOYEE_SETTING: `${brandingConfig.helpArticleDomain}/en/articles/6208620-how-to-set-up-the-fyle-xero-integration-v2-0#h_d70f1d54cc`, + EXPORT_SETTING: `${brandingConfig.helpArticleDomain}/en/articles/6721333-how-to-set-up-the-fyle-xero-integration-v2-0#h_ad07470d98`, + IMPORT_SETTING: `${brandingConfig.helpArticleDomain}/en/articles/6721333-how-to-set-up-the-fyle-xero-integration-v2-0#h_04d289fd42`, + ADVANCED_SETTING: `${brandingConfig.helpArticleDomain}/en/articles/6721333-how-to-set-up-the-fyle-xero-integration-v2-0#h_d95b791edd` } } } @@ -218,7 +216,7 @@ const demoVideoLinks: DemoVideo = { // TODO: Update link for MS Dynamics BUSINESS_CENTRAL: 'https://www.youtube.com/embed/2oYdc8KcQnk', TRAVELPERK: 'https://www.youtube.com/embed/2oYdc8KcQnk', - XERO: 'https://www.youtube.com/embed/2oYdc8KcQnk' + XERO: 'https://www.youtube.com/embed/IplJd7tGWBk' } }, co: { @@ -230,7 +228,7 @@ const demoVideoLinks: DemoVideo = { // TODO: Update link for MS Dynamics BUSINESS_CENTRAL: 'https://www.youtube.com/embed/2oYdc8KcQnk', TRAVELPERK: 'https://www.youtube.com/embed/2oYdc8KcQnk', - XERO: 'https://www.youtube.com/embed/2oYdc8KcQnk' + XERO: 'https://www.youtube.com/embed/IplJd7tGWBk' } } }; diff --git a/src/app/core/models/branding/kb-article.model.ts b/src/app/core/models/branding/kb-article.model.ts index 43791be9c..ffbe06972 100644 --- a/src/app/core/models/branding/kb-article.model.ts +++ b/src/app/core/models/branding/kb-article.model.ts @@ -54,7 +54,6 @@ export type KbArticle = { EXPORT_SETTING: string; IMPORT_SETTING: string; ADVANCED_SETTING: string; - SKIP_EXPORT: string; }, } } diff --git a/src/app/integrations/xero/xero-onboarding/xero-onboarding-landing/xero-onboarding-landing.component.html b/src/app/integrations/xero/xero-onboarding/xero-onboarding-landing/xero-onboarding-landing.component.html index e24f9b45b..79332a4a8 100644 --- a/src/app/integrations/xero/xero-onboarding/xero-onboarding-landing/xero-onboarding-landing.component.html +++ b/src/app/integrations/xero/xero-onboarding/xero-onboarding-landing/xero-onboarding-landing.component.html @@ -1,6 +1,6 @@
- +
@@ -11,7 +11,7 @@ (warningAccepted)="acceptWarning($event)" [isWarningVisible]="isIncorrectXeroConnectedDialogVisible" [headerText]="'Incorrect account selected'" - [contextText]="'You had previously set up the integration with a different QuickBooks Online account. Please choose the same to restore the settings'" + [contextText]="'You had previously set up the integration with a different Xero account. Please choose the same to restore the settings'" [confirmBtnText]="'Re connect'" [showSecondaryCTA]="false"> diff --git a/src/app/shared/components/helper/app-landing-page-header/app-landing-page-header.component.html b/src/app/shared/components/helper/app-landing-page-header/app-landing-page-header.component.html index f72c21819..6e9204ddc 100644 --- a/src/app/shared/components/helper/app-landing-page-header/app-landing-page-header.component.html +++ b/src/app/shared/components/helper/app-landing-page-header/app-landing-page-header.component.html @@ -40,7 +40,7 @@ - @@ -52,6 +52,11 @@ (mouseover)="qboConnectButtonSource = 'assets/buttons/connect-to-qbo-active.svg'" (mouseout)="qboConnectButtonSource = 'assets/buttons/connect-to-qbo.svg'">
+
+ +
diff --git a/src/app/shared/components/helper/app-landing-page-header/app-landing-page-header.component.ts b/src/app/shared/components/helper/app-landing-page-header/app-landing-page-header.component.ts index 8b0f25113..cda9f112a 100644 --- a/src/app/shared/components/helper/app-landing-page-header/app-landing-page-header.component.ts +++ b/src/app/shared/components/helper/app-landing-page-header/app-landing-page-header.component.ts @@ -51,6 +51,8 @@ export class AppLandingPageHeaderComponent implements OnInit { @Input() showQBOButton: boolean; + @Input() showXeroButton: boolean; + @Input() logoWidth: string = '140px'; @Input() logoStyleClasses: string = 'tw-py-10-px tw-px-20-px'; @@ -59,6 +61,8 @@ export class AppLandingPageHeaderComponent implements OnInit { qboConnectButtonSource: string = 'assets/buttons/connect-to-qbo.svg'; + xeroConnectButtonSource: string = 'assets/buttons/connect-to-xero.svg'; + readonly brandingConfig = brandingConfig; readonly isGradientAllowed: boolean = brandingFeatureConfig.isGradientAllowed; diff --git a/src/assets/buttons/connect-to-xero.svg b/src/assets/buttons/connect-to-xero.svg new file mode 100644 index 000000000..4ff22b36f --- /dev/null +++ b/src/assets/buttons/connect-to-xero.svg @@ -0,0 +1 @@ + \ No newline at end of file From 758740255660a096ed9dfab50ea7078d6f768e03 Mon Sep 17 00:00:00 2001 From: Dhaarani <55541808+DhaaraniCIT@users.noreply.github.com> Date: Mon, 18 Mar 2024 12:53:06 +0530 Subject: [PATCH 4/8] Xero tenant connection (#646) xero connector page construction --- src/app/branding/branding-config.ts | 134 +++++++++ src/app/core/guard/tenant.guard.ts | 5 +- .../branding/content-configuration.model.ts | 67 +++++ src/app/core/models/enum/enum.model.ts | 17 +- .../xero/db/xero-tenant-mapping.model.ts | 11 + .../xero-advanced-settings.model.ts | 73 ++++- .../xero-export-settings.model.ts | 188 +++++++++++- .../xero-import-settings.model.ts | 45 ++- .../xero-onboarding.model.ts | 2 +- .../xero-connector.service.ts | 2 +- .../xero-import-settings.service.ts | 5 + ...nboarding-advanced-settings.component.html | 5 +- ...-onboarding-advanced-settings.component.ts | 13 +- .../xero-onboarding-connector.component.html | 44 ++- .../xero-onboarding-connector.component.ts | 246 ++++++++++++++- .../xero-onboarding-done.component.html | 2 +- .../xero-onboarding-done.component.ts | 9 +- ...-onboarding-export-settings.component.html | 5 +- ...ro-onboarding-export-settings.component.ts | 12 +- ...-onboarding-import-settings.component.html | 5 +- ...ro-onboarding-import-settings.component.ts | 12 +- .../xero-onboarding-routing.module.ts | 6 +- .../xero-advanced-settings.component.html | 112 ++++++- .../xero-advanced-settings.component.ts | 116 +++++++- .../xero-export-settings.component.html | 207 ++++++++++++- .../xero-export-settings.component.ts | 136 ++++++++- .../xero-import-settings.component.html | 121 +++++++- .../xero-import-settings.component.ts | 280 +++++++++++++++++- .../xero/xero-shared/xero-shared.module.ts | 4 +- .../configuration-import-field.component.html | 8 +- .../configuration-import-field.component.ts | 27 +- .../configuration-toggle-field.component.html | 2 +- .../configuration-toggle-field.component.ts | 9 + 33 files changed, 1876 insertions(+), 54 deletions(-) diff --git a/src/app/branding/branding-config.ts b/src/app/branding/branding-config.ts index 3c717bc3b..e4cae6e51 100644 --- a/src/app/branding/branding-config.ts +++ b/src/app/branding/branding-config.ts @@ -239,6 +239,73 @@ export const brandingDemoVideoLinks = demoVideoLinks[brandingConfig.brandId]; const content: ContentConfiguration = { fyle: { + xero: { + landing: { + contentText: 'Import data from Xero to ' + brandingConfig.brandName + ' and export expenses from ' + brandingConfig.brandName + ' to Xero. ', + guideHeaderText: 'Guide to setup your integrations' + }, + common: { + readMoreText: 'Read more', + exportLogTabName: 'Export log', + viewExpenseText: 'View expense', + corporateCard: 'Corporate card', + errors: 'errors', + autoMap: 'Auto map', + customField: 'Add new custom field', + customFieldName: 'Field name', + customFieldPlaceholderName: 'Placeholder name', + customFieldType: 'Field type', + customFieldCreateandSave: 'Create and save', + tenantMapping: 'Tenant Mapping', + descriptionText: 'of the description field' + }, + configuration: { + connector: { + configurationHeaderText: 'Connect to Xero Tenant', + configurationSubHeaderText: 'Connect to the Xero Tenant from which you would like to import and export data. The ' + brandingConfig.brandName + ' org and Xero Tenant cannot be changed once the configuration steps are complete.', + stepName: 'Connect to Xero', + subLabel: 'Expenses will be posted to the Xero Tenant Mapping selected here. Once configured, you can not change ' + brandingConfig.brandName + ' organization or Tenant Mapping.' + }, + exportSetting: { + stepName: 'Export settings', + headerText: '', + contentText: 'Enable this to export non-reimbursable expenses from ' + brandingConfig.brandName + '. If not enabled, any corporate credit card expenses will not be exported to Xero.', + corporateCard: { + cccExpenseBankAccountSubLabel: 'The selected expense payment type will be added to the corporate credit card expenses exported from ' + brandingConfig.brandName + ' to Xero.', + creditCardExportTypeSubLabel: '', + expenseState: '', + creditCardExpenseSubLabel: '' + } + }, + importSetting: { + stepName: 'Import Settings', + headerText: '', + contentText: '', + importCategoriesLabel: 'Import the Chart of Accounts as Categories in ', + importCategoriesSubLabel: 'Imported account will be available as Categories in ' + brandingConfig.brandName + '.', + importCustomersLabel: 'Import Customers from Xero', + importCustomersSubLabel: 'The Customers in Xero will be imported as Projects in Fyle and will be a selectable field while creating an expense', + taxCodeLabel: 'Import Tax from Xero', + taxCodeSubLabel: 'The imported Tax codes from Xero will be set as Tax group in ', + defaultTaxCodeLabel: 'Select Default Tax Code', + importSuppliersAsMerchantsLabel: 'Import Suppliers from Xero as Merchants', + notes: 'NOTE: To export billable expenses from Fyle, import Customers from Xero as Projects in Fyle.', + toggleToastMessage: 'You have already mapped a tracking category from Xero to the Project field in '+ brandingConfig.brandName +'. Change the configured mapping to a new field to be able to import Customers in the Project field.' + }, + advancedSettings: { + stepName: 'Advanced settings', + scheduleAutoExport: 'Schedule automatic export', + email: 'Send error notification to', + autoSyncPayments: 'Auto-sync payment status for reimbursable expenses', + defaultPaymentAccount: 'Select payment account', + autoCreateEmployeeVendor: 'Auto-create ', + postEntriesCurrentPeriod: 'Post entries in the current accounting period', + setDescriptionField: 'Set the description field in Xero', + dfvLabel: 'Default field values', + dfvSubLabel: 'If you\'ve made a field mandatory in Xero but don\'t collect a value from your employees in the expense form, you can set a default value here to be added to all the expenses. For location and department, you can opt to use the values from your employee records in Xero.' + } + } + }, intacct: { landing: { contentText: 'Import data from Sage Intacct to ' + brandingConfig.brandName + ' and Export expenses from ' + brandingConfig.brandName + ' to Sage Intacct. ', @@ -416,6 +483,73 @@ const content: ContentConfiguration = { } }, co: { + xero: { + landing: { + contentText: 'Import data from Xero to ' + brandingConfig.brandName + ' and export expenses from ' + brandingConfig.brandName + ' to Xero. ', + guideHeaderText: 'Guide to setup your integrations' + }, + common: { + readMoreText: 'Read more', + exportLogTabName: 'Export log', + viewExpenseText: 'View expense', + corporateCard: 'Corporate card', + errors: 'errors', + autoMap: 'Auto map', + customField: 'Add new custom field', + customFieldName: 'Field name', + customFieldPlaceholderName: 'Placeholder name', + customFieldType: 'Field type', + customFieldCreateandSave: 'Create and save', + tenantMapping: 'Tenant Mapping', + descriptionText: 'of the description field' + }, + configuration: { + connector: { + configurationHeaderText: 'Connect to Xero tenant', + configurationSubHeaderText: 'Connect to the Xero tenant from which you would like to import and export data. The ' + brandingConfig.brandName + ' org and Xero tenant cannot be changed once the configuration steps are complete.', + stepName: 'Connect to Xero', + subLabel: 'Expenses will be posted to the Xero tenant Mapping selected here. Once configured, you can not change ' + brandingConfig.brandName + ' organization or tenant mapping.' + }, + exportSetting: { + stepName: 'Export settings', + headerText: '', + contentText: 'Enable this to export non-reimbursable expenses from ' + brandingConfig.brandName + '. If not enabled, any corporate credit card expenses will not be exported to Xero.', + corporateCard: { + cccExpenseBankAccountSubLabel: '', + creditCardExportTypeSubLabel: '', + expenseState: '', + creditCardExpenseSubLabel: '' + } + }, + importSetting: { + stepName: 'Import Settings', + headerText: '', + contentText: '', + importCategoriesLabel: 'Import the chart of accounts as categories in ', + importCategoriesSubLabel: 'Imported account will be available as categories in ' + brandingConfig.brandName + '.', + importCustomersLabel: 'Import customers from Xero', + importCustomersSubLabel: 'The customers in Xero will be imported as projects in ' + brandingConfig.brandName + ' and will be a selectable field while creating an expense', + taxCodeLabel: 'Import tax from Xero', + taxCodeSubLabel: 'The imported tax codes from Xero will be set as tax group in ', + defaultTaxCodeLabel: 'Select default tax code', + importSuppliersAsMerchantsLabel: 'Import suppliers from Xero as merchants', + notes: 'NOTE: To export billable expenses from ' + brandingConfig.brandName + ', import customers from Xero as projects in ' + brandingConfig.brandName, + toggleToastMessage: 'You have already mapped a tracking category from Xero to the project field in '+ brandingConfig.brandName +'. Change the configured mapping to a new field to be able to import customers in the project field.' + }, + advancedSettings: { + stepName: 'Advanced settings', + scheduleAutoExport: 'Schedule automatic export', + email: 'Send error notification to', + autoSyncPayments: 'Auto-sync payment status for reimbursable expenses', + defaultPaymentAccount: 'Select payment account', + autoCreateEmployeeVendor: 'Auto-create ', + postEntriesCurrentPeriod: 'Post entries in the current accounting period', + setDescriptionField: 'Set the description field in Xero', + dfvLabel: 'Default field values', + dfvSubLabel: 'If you\'ve made a field mandatory in Xero but don\'t collect a value from your employees in the expense form, you can set a default value here to be added to all the expenses. For location and department, you can opt to use the values from your employee records in Xero.' + } + } + }, intacct: { landing: { contentText: 'Import data from Sage Intacct to ' + brandingConfig.brandName + ' and export expenses from ' + brandingConfig.brandName + ' to Sage Intacct. ', diff --git a/src/app/core/guard/tenant.guard.ts b/src/app/core/guard/tenant.guard.ts index eddbbf2a4..e7ab47dec 100644 --- a/src/app/core/guard/tenant.guard.ts +++ b/src/app/core/guard/tenant.guard.ts @@ -36,10 +36,11 @@ export class TenantGuard implements CanActivate { ).pipe( map(response => !!response), catchError(error => { - if (error.status === 400) { + + if (error.status === 404) { globalCacheBusterNotifier.next(); this.toastService.displayToastMessage(ToastSeverity.ERROR, 'Oops! You will need to select a tenant to proceed with the onboarding.'); - return this.router.navigateByUrl('integrations/xero/onboarding/xero_connector'); + return this.router.navigateByUrl('/integrations/xero/onboarding/connector'); } return throwError(error); }) diff --git a/src/app/core/models/branding/content-configuration.model.ts b/src/app/core/models/branding/content-configuration.model.ts index a089e8fcc..01fc2a3df 100644 --- a/src/app/core/models/branding/content-configuration.model.ts +++ b/src/app/core/models/branding/content-configuration.model.ts @@ -1,5 +1,72 @@ export type ContentConfiguration = { [brandingId: string]: { + xero: { + landing: { + contentText: string; + guideHeaderText: string; + }, + common: { + readMoreText: string; + exportLogTabName: string; + viewExpenseText: string; + corporateCard: string; + errors: string; + autoMap: string; + customField: string; + customFieldName: string; + customFieldPlaceholderName: string; + customFieldType: string; + customFieldCreateandSave: string; + tenantMapping: string; + descriptionText: string; + }, + configuration: { + connector: { + stepName: string; + subLabel: string; + configurationHeaderText: string; + configurationSubHeaderText: string; + }, + exportSetting: { + stepName: string; + headerText: string; + contentText: string; + corporateCard: { + cccExpenseBankAccountSubLabel: string; + creditCardExportTypeSubLabel: string; + expenseState: string; + creditCardExpenseSubLabel: string + } + }, + importSetting: { + stepName: string; + headerText: string; + contentText: string; + importCategoriesLabel: string; + importCategoriesSubLabel: string; + importCustomersLabel: string; + importCustomersSubLabel: string; + taxCodeLabel: string; + taxCodeSubLabel: string; + defaultTaxCodeLabel: string; + importSuppliersAsMerchantsLabel: string; + notes: string, + toggleToastMessage: string + }, + advancedSettings: { + stepName: string; + scheduleAutoExport: string; + email: string; + autoSyncPayments: string; + defaultPaymentAccount: string; + autoCreateEmployeeVendor: string; + postEntriesCurrentPeriod: string; + setDescriptionField: string; + dfvLabel: string; + dfvSubLabel: string; + } + }, + }, intacct : { landing: { contentText: string; diff --git a/src/app/core/models/enum/enum.model.ts b/src/app/core/models/enum/enum.model.ts index 54159772a..48580322c 100644 --- a/src/app/core/models/enum/enum.model.ts +++ b/src/app/core/models/enum/enum.model.ts @@ -246,9 +246,16 @@ export enum ExpenseState { PAID = 'PAID' } + export enum CCCExpenseState { PAID = 'PAID', - APPROVED = 'APPROVED' + APPROVED = 'APPROVED', +} + +export enum XeroCCCExpenseState { + PAID = 'PAID', + APPROVED = 'APPROVED', + PAYMENT_PROCESSING = "PAYMENT_PROCESSING" } export enum ExpenseGroupedBy { @@ -336,6 +343,13 @@ export enum QBDFyleField { COST_CENTER = 'COST_CENTER' } +export enum XeroFyleField { + PROJECT = 'PROJECT', + CUSTOMER = 'CUSTOMER', + TAX_CODE = 'TAX_CODE', + BANK_ACCOUNT = 'BANK_ACCOUNT' +} + export enum QBDAccountingExportsState { COMPLETE = 'COMPLETE', ENQUEUED = 'ENQUEUED', @@ -648,6 +662,7 @@ export enum ConfigurationWarningEvent { CLONE_SETTINGS = 'CLONE_SETTINGS', INCORRECT_QBO_ACCOUNT_CONNECTED = 'INCORRECT_QBO_ACCOUNT_CONNECTED', QBO_EXPORT_SETTINGS = 'QBO_EXPORT_SETTINGS', + XERO_EXPORT_SETTINGS = 'XERO_EXPORT_SETTINGS', RESET_CONFIGURATION = 'RESET_CONFIGURATION' } diff --git a/src/app/core/models/xero/db/xero-tenant-mapping.model.ts b/src/app/core/models/xero/db/xero-tenant-mapping.model.ts index 891236ab3..a413fca14 100644 --- a/src/app/core/models/xero/db/xero-tenant-mapping.model.ts +++ b/src/app/core/models/xero/db/xero-tenant-mapping.model.ts @@ -1,3 +1,5 @@ +import { DestinationAttribute } from "../../db/destination-attribute.model"; + /* Tslint:disable */ export type TenantMapping = { id: number; @@ -13,3 +15,12 @@ export type TenantMappingPost = { tenant_id: string; tenant_name: string; } + +export class TenantMappingModel { + static constructPayload(tenantMapping: DestinationAttribute): TenantMappingPost { + return { + tenant_id: tenantMapping.id.toString(), + tenant_name: tenantMapping.value + }; + } +} diff --git a/src/app/core/models/xero/xero-configuration/xero-advanced-settings.model.ts b/src/app/core/models/xero/xero-configuration/xero-advanced-settings.model.ts index cd6b38a46..c2cd37376 100644 --- a/src/app/core/models/xero/xero-configuration/xero-advanced-settings.model.ts +++ b/src/app/core/models/xero/xero-configuration/xero-advanced-settings.model.ts @@ -1,8 +1,9 @@ -import { FormGroup } from "@angular/forms"; -import { EmailOption } from "../../common/advanced-settings.model"; +import { FormControl, FormGroup } from "@angular/forms"; +import { AdvancedSettingValidatorRule, AdvancedSettingsModel, EmailOption } from "../../common/advanced-settings.model"; import { SelectFormOption } from "../../common/select-form-option.model"; import { DefaultDestinationAttribute } from "../../db/destination-attribute.model"; import { PaymentSyncDirection } from "../../enum/enum.model"; +import { HelperUtility } from "../../common/helper.model"; export type XeroAdvancedSettingWorkspaceGeneralSetting = { @@ -56,7 +57,69 @@ export interface XeroAdvancedSettingFormOption extends SelectFormOption { value: PaymentSyncDirection | number | 'None'; } -export class XeroAdvancedSettingModel { +export class XeroAdvancedSettingModel extends HelperUtility{ + + static getPaymentSyncOptions(): SelectFormOption[] { + return [ + { + label: 'None', + value: 'None' + }, + { + label: 'Export Fyle ACH Payments to Xero', + value: PaymentSyncDirection.FYLE_TO_XERO + }, + { + label: 'Import Xero Payments into Fyle', + value: PaymentSyncDirection.XERO_TO_FYLE + } + ]; + } + + static getValidators(): AdvancedSettingValidatorRule { + return { + paymentSync: 'billPaymentAccount', + exportSchedule: 'exportScheduleFrequency' + }; + } + + static setConfigurationSettingValidatorsAndWatchers(form: FormGroup): void { + const validatorRule = this.getValidators(); + const keys = Object.keys(validatorRule); + + Object.values(validatorRule).forEach((value, index) => { + form.controls[keys[index]].valueChanges.subscribe((selectedValue) => { + if (selectedValue && ((keys[index] === 'paymentSync' && selectedValue === PaymentSyncDirection.FYLE_TO_XERO) || (keys[index] !== 'paymentSync'))) { + this.markControllerAsRequired(form, value); + } else { + this.clearValidatorAndResetValue(form, value); + } + }); + }); + } + + static mapAPIResponseToFormGroup(advancedSettings: XeroAdvancedSettingGet, adminEmails: EmailOption[]): FormGroup { + let paymentSync = ''; + if (advancedSettings.workspace_general_settings.sync_fyle_to_xero_payments) { + paymentSync = PaymentSyncDirection.FYLE_TO_XERO; + } else if (advancedSettings.workspace_general_settings.sync_xero_to_fyle_payments) { + paymentSync = PaymentSyncDirection.XERO_TO_FYLE; + } + return new FormGroup({ + paymentSync: new FormControl(paymentSync), + billPaymentAccount: new FormControl(advancedSettings.general_mappings.payment_account.id ? advancedSettings.general_mappings.payment_account : null), + changeAccountingPeriod: new FormControl(advancedSettings.workspace_general_settings.change_accounting_period), + autoCreateVendors: new FormControl(advancedSettings.workspace_general_settings.auto_create_destination_entity), + exportSchedule: new FormControl(advancedSettings.workspace_schedules?.enabled ? advancedSettings.workspace_schedules.interval_hours : false), + exportScheduleFrequency: new FormControl(advancedSettings.workspace_schedules?.enabled ? advancedSettings.workspace_schedules.interval_hours : 1), + autoCreateMerchantDestinationEntity: new FormControl(advancedSettings.workspace_general_settings.auto_create_merchant_destination_entity ? advancedSettings.workspace_general_settings.auto_create_merchant_destination_entity : false), + search: new FormControl(), + searchOption: new FormControl(), + email: new FormControl(advancedSettings?.workspace_schedules?.emails_selected && advancedSettings?.workspace_schedules?.emails_selected?.length > 0 ? AdvancedSettingsModel.filterAdminEmails(advancedSettings?.workspace_schedules?.emails_selected, adminEmails) : []), + additionalEmails: new FormControl([]) + }); + } + static constructPayload(advancedSettingsForm: FormGroup): XeroAdvancedSettingPost { const emptyDestinationAttribute = {id: null, name: null}; const advancedSettingPayload: XeroAdvancedSettingPost = { @@ -74,8 +137,8 @@ export class XeroAdvancedSettingModel { enabled: advancedSettingsForm.get('exportSchedule')?.value ? true : false, interval_hours: advancedSettingsForm.get('exportScheduleFrequency')?.value ? advancedSettingsForm.get('exportScheduleFrequency')?.value : null, start_datetime: new Date(), - emails_selected: advancedSettingsForm.get('emails')?.value ? advancedSettingsForm.get('emails')?.value : [], - additional_email_options: advancedSettingsForm.get('addedEmail')?.value ? advancedSettingsForm.get('addedEmail')?.value : [] + emails_selected: advancedSettingsForm.get('email')?.value ? AdvancedSettingsModel.formatSelectedEmails(advancedSettingsForm.get('email')?.value) : [], + additional_email_options: advancedSettingsForm.get('additionalEmails')?.value ? advancedSettingsForm.get('additionalEmails')?.value : [] } }; return advancedSettingPayload; diff --git a/src/app/core/models/xero/xero-configuration/xero-export-settings.model.ts b/src/app/core/models/xero/xero-configuration/xero-export-settings.model.ts index 06d5de6e7..30438a0a4 100644 --- a/src/app/core/models/xero/xero-configuration/xero-export-settings.model.ts +++ b/src/app/core/models/xero/xero-configuration/xero-export-settings.model.ts @@ -1,10 +1,20 @@ -import { FormGroup } from "@angular/forms"; +import { FormControl, FormGroup } from "@angular/forms"; import { SelectFormOption } from "../../common/select-form-option.model"; import { DefaultDestinationAttribute } from "../../db/destination-attribute.model"; import { ExpenseGroupSettingGet, ExpenseGroupSettingPost } from "../../db/expense-group-setting.model"; -import { AutoMapEmployeeOptions, CCCExpenseState, ExpenseGroupingFieldOption, ExpenseState, ExportDateType, XeroCorporateCreditCardExpensesObject, XeroReimbursableExpensesObject } from "../../enum/enum.model"; -import { ExportSettingGeneralMapping } from "../../intacct/intacct-configuration/export-settings.model"; +import { AutoMapEmployeeOptions, ExpenseGroupingFieldOption, ExpenseState, ExportDateType, XeroCCCExpenseState, XeroCorporateCreditCardExpensesObject, XeroReimbursableExpensesObject } from "../../enum/enum.model"; +import { ExportModuleRule, ExportSettingValidatorRule } from "../../common/export-settings.model"; +export type XeroExpenseGroupSettingPost = { + ccc_expense_state: XeroCCCExpenseState; + reimbursable_expense_group_fields?: string[] | null; + reimbursable_export_date_type: ExportDateType | null; + corporate_credit_card_expense_group_fields?: string[] | null; + ccc_export_date_type: ExportDateType | null; + reimbursable_expense_state: ExpenseState +}; + +export interface XeroExpenseGroupSettingGet extends XeroExpenseGroupSettingPost {} export type XeroExportSettingWorkspaceGeneralSettingPost = { reimbursable_expenses_object: XeroReimbursableExpensesObject | null, @@ -21,28 +31,186 @@ export type XeroExportSettingGeneralMapping = { } export type XeroExportSettingPost = { - expense_group_settings: ExpenseGroupSettingPost, + expense_group_settings: XeroExpenseGroupSettingPost, workspace_general_settings: XeroExportSettingWorkspaceGeneralSettingPost, general_mappings: XeroExportSettingGeneralMapping } export type XeroExportSettingGet = { - expense_group_settings: ExpenseGroupSettingGet, + expense_group_settings: XeroExpenseGroupSettingGet, workspace_general_settings: XeroExportSettingWorkspaceGeneralSetting, - general_mappings: ExportSettingGeneralMapping, + general_mappings: XeroExportSettingGeneralMapping, workspace_id: number } -export interface XeroExportSettingFormOption extends SelectFormOption { - value: ExpenseState | CCCExpenseState | XeroReimbursableExpensesObject | XeroCorporateCreditCardExpensesObject | ExpenseGroupingFieldOption | ExportDateType | AutoMapEmployeeOptions | null; +export interface XeroSelectFormOption extends SelectFormOption { + value: ExpenseState | XeroCCCExpenseState | XeroReimbursableExpensesObject | XeroCorporateCreditCardExpensesObject | ExpenseGroupingFieldOption | ExportDateType | AutoMapEmployeeOptions | null; } export class XeroExportSettingModel { + + static getReimbursableExportTypes() { + return [ + { + label: 'Purchase Bill', + value: XeroReimbursableExpensesObject.PURCHASE_BILL + } + ]; + } + + static getCreditCardExportTypes() { + return [ + { + label: 'Bank Transaction', + value: XeroCorporateCreditCardExpensesObject.BANK_TRANSACTION + } + ]; + } + + static getReimbursableExpenseGroupingOptions(): SelectFormOption[] { + return [ + { + label: 'Report', + value: ExpenseGroupingFieldOption.CLAIM_NUMBER + } + ]; + } + + static getCCCExpenseGroupingOptions(): SelectFormOption[] { + return [ + { + label: 'Expense', + value: ExpenseGroupingFieldOption.EXPENSE_ID + } + ]; + } + + static getAutoMapEmployeeOptions(): SelectFormOption[] { + return [ + { + label: 'None', + value: null + }, + { + label: 'Employee name on Fyle to contact name on Xero', + value: AutoMapEmployeeOptions.NAME + }, + { + label: 'Employee email on Fyle to contact email on Xero', + value: AutoMapEmployeeOptions.EMAIL + } + ]; + } + + static getReimbursableExpenseGroupingDateOptions(): SelectFormOption[] { + return [ + { + label: 'Current Date', + value: ExportDateType.CURRENT_DATE + }, + { + label: 'Verification Date', + value: ExportDateType.VERIFIED_AT + }, + { + label: 'Spend Date', + value: ExportDateType.SPENT_AT + }, + { + label: 'Approval Date', + value: ExportDateType.APPROVED_AT + }, + { + label: 'Last Spend Date', + value: ExportDateType.LAST_SPENT_AT + } + ]; + } + + static getCCCExpenseGroupingDateOptions(): SelectFormOption[] { + return [ + { + label: 'Spend Date', + value: ExportDateType.SPENT_AT + }, + { + label: 'Card Transaction Post date', + value: ExportDateType.POSTED_AT + } + ]; + } + + static getReimbursableExpenseStateOptions(): SelectFormOption[] { + return [ + { + label: 'Processing', + value: ExpenseState.PAYMENT_PROCESSING + }, + { + label: 'Closed', + value: ExpenseState.PAID + } + ]; + } + + static getCCCExpenseStateOptions(): SelectFormOption[] { + return [ + { + label: 'Payment Processing', + value: XeroCCCExpenseState.APPROVED + }, + { + label: 'Closed', + value: XeroCCCExpenseState.PAID + } + ]; + } + + static getValidators(): [ExportSettingValidatorRule, ExportModuleRule[]] { + const exportSettingValidatorRule: ExportSettingValidatorRule = { + reimbursableExpense: ['reimbursableExportType', 'reimbursableExportGroup', 'reimbursableExportDate', 'expenseState'], + creditCardExpense: ['creditCardExportType', 'creditCardExportGroup', 'creditCardExportDate', 'cccExpenseState', 'bankAccount'] + }; + + const exportModuleRule: ExportModuleRule[] = [ + { + formController: 'reimbursableExportType', + requiredValue: { + } + }, + { + formController: 'creditCardExportType', + requiredValue: { + } + } + ]; + + return [exportSettingValidatorRule, exportModuleRule]; + } + + static mapAPIResponseToFormGroup(exportSettings: XeroExportSettingGet | null): FormGroup { + return new FormGroup({ + expenseState: new FormControl(exportSettings?.expense_group_settings?.reimbursable_expense_state), + reimbursableExpense: new FormControl(exportSettings?.workspace_general_settings?.reimbursable_expenses_object ? true : false), + reimbursableExportType: new FormControl(exportSettings?.workspace_general_settings?.reimbursable_expenses_object ? exportSettings?.workspace_general_settings?.reimbursable_expenses_object : XeroReimbursableExpensesObject.PURCHASE_BILL), + reimbursableExportGroup: new FormControl(ExpenseGroupingFieldOption.CLAIM_NUMBER), + reimbursableExportDate: new FormControl(exportSettings?.expense_group_settings?.reimbursable_export_date_type), + cccExpenseState: new FormControl(exportSettings?.expense_group_settings?.ccc_expense_state), + creditCardExpense: new FormControl(exportSettings?.workspace_general_settings?.corporate_credit_card_expenses_object ? true : false), + creditCardExportType: new FormControl(exportSettings?.workspace_general_settings?.corporate_credit_card_expenses_object ? exportSettings?.workspace_general_settings?.corporate_credit_card_expenses_object : XeroCorporateCreditCardExpensesObject.BANK_TRANSACTION), + creditCardExportGroup: new FormControl(ExpenseGroupingFieldOption.EXPENSE_ID), + creditCardExportDate: new FormControl(exportSettings?.expense_group_settings?.ccc_export_date_type), + bankAccount: new FormControl(exportSettings?.general_mappings?.bank_account?.id ? exportSettings.general_mappings.bank_account : null), + autoMapEmployees: new FormControl(exportSettings?.workspace_general_settings?.auto_map_employees), + searchOption: new FormControl('') + }); + } + static constructPayload(exportSettingsForm: FormGroup): XeroExportSettingPost { const emptyDestinationAttribute = {id: null, name: null}; const exportSettingPayload: XeroExportSettingPost = { expense_group_settings: { - expense_state: exportSettingsForm.get('reimbursableExpenseState')?.value, + reimbursable_expense_state: exportSettingsForm.get('expenseState')?.value, reimbursable_export_date_type: exportSettingsForm.get('reimbursableExportDate')?.value ? exportSettingsForm.get('reimbursableExportDate')?.value : ExportDateType.CURRENT_DATE, ccc_expense_state: exportSettingsForm.get('cccExpenseState')?.value, ccc_export_date_type: exportSettingsForm.get('cccExportDate')?.value ? exportSettingsForm.get('cccExportDate')?.value : ExportDateType.SPENT_AT @@ -50,7 +218,7 @@ export class XeroExportSettingModel { workspace_general_settings: { reimbursable_expenses_object: exportSettingsForm.get('reimbursableExpense')?.value ? XeroReimbursableExpensesObject.PURCHASE_BILL : null, corporate_credit_card_expenses_object: exportSettingsForm.get('creditCardExpense')?.value ? XeroCorporateCreditCardExpensesObject.BANK_TRANSACTION : null, - auto_map_employees: exportSettingsForm.get('AutoMapEmployeeOptionss')?.value + auto_map_employees: exportSettingsForm.get('autoMapEmployees')?.value }, general_mappings: { bank_account: exportSettingsForm.get('bankAccount')?.value ? exportSettingsForm.get('bankAccount')?.value : emptyDestinationAttribute diff --git a/src/app/core/models/xero/xero-configuration/xero-import-settings.model.ts b/src/app/core/models/xero/xero-configuration/xero-import-settings.model.ts index 10bbff8b3..a4192ef7b 100644 --- a/src/app/core/models/xero/xero-configuration/xero-import-settings.model.ts +++ b/src/app/core/models/xero/xero-configuration/xero-import-settings.model.ts @@ -1,12 +1,13 @@ -import { FormGroup } from "@angular/forms"; +import { FormArray, FormControl, FormGroup } from "@angular/forms"; import { SelectFormOption } from "../../common/select-form-option.model"; import { DefaultDestinationAttribute } from "../../db/destination-attribute.model"; import { MappingSetting } from "../../db/mapping-setting.model"; -import { MappingDestinationField, MappingSourceField } from "../../enum/enum.model"; -import { GeneralMapping } from "../../intacct/db/mappings.model"; +import { MappingDestinationField, MappingSourceField, XeroFyleField } from "../../enum/enum.model"; import { ImportSettingGeneralMapping } from "../../intacct/intacct-configuration/import-settings.model"; import { XeroWorkspaceGeneralSetting } from "../db/xero-workspace-general-setting.model"; -import { ImportSettingsModel } from "../../common/import-settings.model"; +import { ImportSettingMappingRow, ImportSettingsModel } from "../../common/import-settings.model"; +import { IntegrationField } from "../../db/mapping.model"; +import { brandingConfig } from "src/app/branding/branding-config"; export type XeroImportSettingWorkspaceGeneralSetting = { @@ -45,7 +46,7 @@ export type ExpenseFieldsFormOption = { export type XeroImportSettingGet = { workspace_general_settings: XeroWorkspaceGeneralSetting, - general_mappings: GeneralMapping, + general_mappings: XeroImportSettingGeneralMapping, mapping_settings: MappingSetting[], workspace_id:number } @@ -56,16 +57,46 @@ export interface XeroImportSettingFormOption extends SelectFormOption { export class XeroImportSettingModel extends ImportSettingsModel { + + static getChartOfAccountTypesList(): string[] { + return ['EXPENSE', 'ASSET', 'EQUITY', 'LIABILITY', 'REVENUE']; + } + + static mapAPIResponseToFormGroup(importSettings: XeroImportSettingGet | null, xeroFields: IntegrationField[], isCustomerPresent:boolean): FormGroup { + let additionalOption: any[] = []; + if (brandingConfig.brandId === 'co' && isCustomerPresent) { + const additionalMappingSetting = { + source_field: 'DISABLED_XERO_SOURCE_FIELD', + destination_field: XeroFyleField.CUSTOMER, + import_to_fyle: importSettings?.workspace_general_settings.import_customers || false, + is_custom: false, + source_placeholder: null + }; + additionalOption = [ImportSettingsModel.createFormGroup(additionalMappingSetting)]; + } + const expenseFieldsArray = importSettings?.mapping_settings ? additionalOption.concat(this.constructFormArray(importSettings.mapping_settings, xeroFields)) : []; + return new FormGroup({ + importCategories: new FormControl(importSettings?.workspace_general_settings.import_categories ?? false), + expenseFields: new FormArray(expenseFieldsArray), + chartOfAccountTypes: new FormControl(importSettings?.workspace_general_settings.charts_of_accounts ? importSettings.workspace_general_settings.charts_of_accounts : ['Expense']), + importCustomers: new FormControl(importSettings?.workspace_general_settings.import_customers ?? false), + taxCode: new FormControl(importSettings?.workspace_general_settings.import_tax_codes ?? false), + importSuppliersAsMerchants: new FormControl(importSettings?.workspace_general_settings.import_suppliers_as_merchants ?? false), + defaultTaxCode: new FormControl(importSettings?.general_mappings?.default_tax_code?.id ? importSettings.general_mappings.default_tax_code : null), + searchOption: new FormControl('') + }); + } + static constructPayload(importSettingsForm: FormGroup): XeroImportSettingPost { const emptyDestinationAttribute = {id: null, name: null}; const chartOfAccounts = XeroImportSettingModel.formatChartOfAccounts(importSettingsForm.get('chartOfAccountTypes')?.value); - const expenseFieldArray = importSettingsForm.getRawValue().expenseFields; + const expenseFieldArray = importSettingsForm.getRawValue().expenseFields.filter(((data:any) => data.destination_field !== XeroFyleField.CUSTOMER)); const mappingSettings = this.constructMappingSettingPayload(expenseFieldArray); const importSettingPayload: XeroImportSettingPost = { workspace_general_settings: { - import_categories: importSettingsForm.get('chartOfAccount')?.value, + import_categories: importSettingsForm.get('chartOfAccount')?.value ?? false, charts_of_accounts: importSettingsForm.get('chartOfAccount')?.value ? chartOfAccounts : ['Expense'], import_tax_codes: importSettingsForm.get('taxCode')?.value, import_suppliers_as_merchants: importSettingsForm.get('importSuppliersAsMerchants')?.value, diff --git a/src/app/core/models/xero/xero-configuration/xero-onboarding.model.ts b/src/app/core/models/xero/xero-configuration/xero-onboarding.model.ts index ea86aeaff..fd1aaa5c5 100644 --- a/src/app/core/models/xero/xero-configuration/xero-onboarding.model.ts +++ b/src/app/core/models/xero/xero-configuration/xero-onboarding.model.ts @@ -21,7 +21,7 @@ export class XeroOnboardingModel { step: 'Connect to Xero', icon: 'link-vertical-medium', route: '/integrations/xero/onboarding/connector', - styleClasses: ['step-name-connector--text'] + styleClasses: ['step-name-connector--text tw-pl-24-px'] }, { active: false, diff --git a/src/app/core/services/xero/xero-configuration/xero-connector.service.ts b/src/app/core/services/xero/xero-configuration/xero-connector.service.ts index e1e6c9d94..9df958633 100644 --- a/src/app/core/services/xero/xero-configuration/xero-connector.service.ts +++ b/src/app/core/services/xero/xero-configuration/xero-connector.service.ts @@ -5,8 +5,8 @@ import { WorkspaceService } from '../../common/workspace.service'; import { CacheBuster, Cacheable, globalCacheBusterNotifier } from 'ts-cacheable'; import { XeroCredentials } from 'src/app/core/models/xero/db/xero-credential.model'; import { environment } from 'src/environments/environment'; -import { DestinationAttribute } from 'src/app/core/models/db/destination-attribute.model'; import { TenantMapping, TenantMappingPost } from 'src/app/core/models/xero/db/xero-tenant-mapping.model'; +import { DestinationAttribute } from 'src/app/core/models/db/destination-attribute.model'; const xeroCredentialsCache = new Subject(); diff --git a/src/app/core/services/xero/xero-configuration/xero-import-settings.service.ts b/src/app/core/services/xero/xero-configuration/xero-import-settings.service.ts index ffd740ae2..ac37b1ba9 100644 --- a/src/app/core/services/xero/xero-configuration/xero-import-settings.service.ts +++ b/src/app/core/services/xero/xero-configuration/xero-import-settings.service.ts @@ -4,6 +4,7 @@ import { WorkspaceService } from '../../common/workspace.service'; import { XeroImportSettingGet, XeroImportSettingPost } from 'src/app/core/models/xero/xero-configuration/xero-import-settings.model'; import { Observable, Subject } from 'rxjs'; import { CacheBuster, Cacheable } from 'ts-cacheable'; +import { IntegrationField } from 'src/app/core/models/db/mapping.model'; const xeroImportSettingGetCache$ = new Subject(); @@ -30,4 +31,8 @@ export class XeroImportSettingsService { postImportSettings(exportSettingsPayload: XeroImportSettingPost): Observable{ return this.apiService.put(`/v2/workspaces/${this.workspaceService.getWorkspaceId()}/import_settings/`, exportSettingsPayload); } + + getXeroField(): Observable { + return this.apiService.get(`/workspaces/${this.workspaceService.getWorkspaceId()}/xero/xero_fields/`, {}); + } } diff --git a/src/app/integrations/xero/xero-onboarding/xero-onboarding-advanced-settings/xero-onboarding-advanced-settings.component.html b/src/app/integrations/xero/xero-onboarding/xero-onboarding-advanced-settings/xero-onboarding-advanced-settings.component.html index 7065c19e6..70cdc677c 100644 --- a/src/app/integrations/xero/xero-onboarding/xero-onboarding-advanced-settings/xero-onboarding-advanced-settings.component.html +++ b/src/app/integrations/xero/xero-onboarding/xero-onboarding-advanced-settings/xero-onboarding-advanced-settings.component.html @@ -1 +1,4 @@ -

xero-onboarding-advanced-settings works!

+
+ + +
diff --git a/src/app/integrations/xero/xero-onboarding/xero-onboarding-advanced-settings/xero-onboarding-advanced-settings.component.ts b/src/app/integrations/xero/xero-onboarding/xero-onboarding-advanced-settings/xero-onboarding-advanced-settings.component.ts index 0a88b4fae..5291c3320 100644 --- a/src/app/integrations/xero/xero-onboarding/xero-onboarding-advanced-settings/xero-onboarding-advanced-settings.component.ts +++ b/src/app/integrations/xero/xero-onboarding/xero-onboarding-advanced-settings/xero-onboarding-advanced-settings.component.ts @@ -1,4 +1,8 @@ import { Component, OnInit } from '@angular/core'; +import { brandingContent } from 'src/app/branding/branding-config'; +import { OnboardingStepper } from 'src/app/core/models/misc/onboarding-stepper.model'; +import { XeroOnboardingModel } from 'src/app/core/models/xero/xero-configuration/xero-onboarding.model'; +import { WorkspaceService } from 'src/app/core/services/common/workspace.service'; @Component({ selector: 'app-xero-onboarding-advanced-settings', @@ -7,7 +11,14 @@ import { Component, OnInit } from '@angular/core'; }) export class XeroOnboardingAdvancedSettingsComponent implements OnInit { - constructor() { } + brandingContent = brandingContent.xero.configuration.advancedSettings; + + onboardingSteps: OnboardingStepper[] = new XeroOnboardingModel().getOnboardingSteps(this.brandingContent.stepName, this.workspaceService.getOnboardingState()); + + constructor( + private workspaceService: WorkspaceService + ) { } + ngOnInit(): void { } diff --git a/src/app/integrations/xero/xero-onboarding/xero-onboarding-connector/xero-onboarding-connector.component.html b/src/app/integrations/xero/xero-onboarding/xero-onboarding-connector/xero-onboarding-connector.component.html index aa6ca777b..05d4496d2 100644 --- a/src/app/integrations/xero/xero-onboarding/xero-onboarding-connector/xero-onboarding-connector.component.html +++ b/src/app/integrations/xero/xero-onboarding/xero-onboarding-connector/xero-onboarding-connector.component.html @@ -1 +1,43 @@ -

xero-onboarding-connector works!

+
+ +
+
+ +
+
+
+ + +
+
+ + + + + +
+ +
+
+
diff --git a/src/app/integrations/xero/xero-onboarding/xero-onboarding-connector/xero-onboarding-connector.component.ts b/src/app/integrations/xero/xero-onboarding/xero-onboarding-connector/xero-onboarding-connector.component.ts index e4b31e35b..2b4a5a2a4 100644 --- a/src/app/integrations/xero/xero-onboarding/xero-onboarding-connector/xero-onboarding-connector.component.ts +++ b/src/app/integrations/xero/xero-onboarding/xero-onboarding-connector/xero-onboarding-connector.component.ts @@ -1,4 +1,26 @@ import { Component, OnInit } from '@angular/core'; +import { Router } from '@angular/router'; +import { Subscription } from 'rxjs'; +import { brandingConfig, brandingContent, brandingFeatureConfig, brandingKbArticles } from 'src/app/branding/branding-config'; +import { BrandingConfiguration } from 'src/app/core/models/branding/branding-configuration.model'; +import { CloneSettingExist } from 'src/app/core/models/common/clone-setting.model'; +import { DestinationAttribute } from 'src/app/core/models/db/destination-attribute.model'; +import { ConfigurationCta, ConfigurationWarningEvent, ToastSeverity, XeroOnboardingState } from 'src/app/core/models/enum/enum.model'; +import { ConfigurationWarningOut } from 'src/app/core/models/misc/configuration-warning.model'; +import { OnboardingStepper } from 'src/app/core/models/misc/onboarding-stepper.model'; +import { XeroCredentials } from 'src/app/core/models/xero/db/xero-credential.model'; +import { TenantMapping, TenantMappingModel, TenantMappingPost } from 'src/app/core/models/xero/db/xero-tenant-mapping.model'; +import { XeroExportSettingGet } from 'src/app/core/models/xero/xero-configuration/xero-export-settings.model'; +import { XeroOnboardingModel } from 'src/app/core/models/xero/xero-configuration/xero-onboarding.model'; +import { CloneSettingService } from 'src/app/core/services/common/clone-setting.service'; +import { HelperService } from 'src/app/core/services/common/helper.service'; +import { IntegrationsToastService } from 'src/app/core/services/common/integrations-toast.service'; +import { WorkspaceService } from 'src/app/core/services/common/workspace.service'; +import { UserService } from 'src/app/core/services/misc/user.service'; +import { XeroConnectorService } from 'src/app/core/services/xero/xero-configuration/xero-connector.service'; +import { XeroExportSettingsService } from 'src/app/core/services/xero/xero-configuration/xero-export-settings.service'; +import { XeroHelperService } from 'src/app/core/services/xero/xero-core/xero-helper.service'; +import { environment } from 'src/environments/environment'; @Component({ selector: 'app-xero-onboarding-connector', @@ -7,9 +29,231 @@ import { Component, OnInit } from '@angular/core'; }) export class XeroOnboardingConnectorComponent implements OnInit { - constructor() { } + brandingContent = brandingContent.xero.configuration.connector; + + onboardingSteps: OnboardingStepper[] = new XeroOnboardingModel().getOnboardingSteps(this.brandingContent.stepName, this.workspaceService.getOnboardingState()); + + isLoading: boolean = true; + + redirectLink: string = brandingKbArticles.onboardingArticles.XERO.CONNECTOR; + + brandingConfig: BrandingConfiguration = brandingConfig; + + ConfigurationCtaText = ConfigurationCta; + + saveInProgress: boolean = false; + + xeroConnectionInProgress: boolean = false; + + xeroCompanyName: string | null; + + isContinueDisabled: boolean = true; + + showDisconnectQBO: boolean = false; + + isWarningDialogVisible: boolean = false; + + xeroTokenExpired: boolean = false; + + isXeroConnected: boolean = false; + + private oauthCallbackSubscription: Subscription; + + readonly fyleOrgName: string = this.userService.getUserProfile().org_name; + + private isCloneSettingsDisabled: boolean; + + warningHeaderText: string; + + warningContextText: string; + + primaryButtonText: string; + + warningEvent: ConfigurationWarningEvent; + + showDisconnectXero: boolean; + + tenantList: DestinationAttribute[]; + + xeroTenantselected: DestinationAttribute; + + constructor( + private workspaceService: WorkspaceService, + private userService: UserService, + private xeroConnectorService: XeroConnectorService, + private exportSettingService: XeroExportSettingsService, + private helperService: HelperService, + private router: Router, + private toastService: IntegrationsToastService, + private cloneSettingService: CloneSettingService, + private xeroHelperService: XeroHelperService + ) { } + + private checkCloneSettingsAvailablity(): void { + this.cloneSettingService.checkCloneSettingsExists().subscribe((response: CloneSettingExist) => { + if (response.is_available) { + this.warningHeaderText = 'Your settings are pre-filled'; + this.warningContextText = `Your previous organization's settings (${response.workspace_name}) have been copied over to the current organization +

You can change the settings or reset the configuration to restart the process from the beginning
`; + this.primaryButtonText = 'Continue'; + this.warningEvent = ConfigurationWarningEvent.CLONE_SETTINGS; + this.isWarningDialogVisible = true; + this.isContinueDisabled = false; + this.isCloneSettingsDisabled = true; + } else { + this.router.navigate(['/integrations/xero/onboarding/export_settings']); + } + }); + } + + disconnectXero(): void { + this.isLoading = true; + this.xeroConnectorService.revokeXeroConnection(this.workspaceService.getWorkspaceId()).subscribe(() => { + this.showDisconnectXero = false; + this.xeroCompanyName = null; + this.xeroConnectionInProgress = false; + this.isXeroConnected = false; + this.isContinueDisabled = true; + this.xeroConnectorService.getXeroCredentials(this.workspaceService.getWorkspaceId()).subscribe((xeroCredentials: XeroCredentials) => { + this.showOrHideDisconnectXero(); + }, (error) => { + // Token expired + if ('id' in error.error) { + // We have a Xero row present in DB + this.xeroTokenExpired = error.error.is_expired; + if (this.xeroTokenExpired) { + this.xeroCompanyName = error.error.company_name; + } + } + this.isContinueDisabled = true; + this.isXeroConnected = false; + this.isLoading = false; + }); + }); + } + + private postXeroCredentials(code: string): void { + this.xeroConnectorService.connectXero(this.workspaceService.getWorkspaceId(), code).subscribe((xeroCredentials: XeroCredentials) => { + this.isXeroConnected = true; + this.xeroConnectionInProgress = false; + }, (error) => { + const errorMessage = 'message' in error.error ? error.error.message : 'Failed to connect to Xero Tenant. Please try again'; + if (errorMessage === 'Please choose the correct Xero Tenten') { + this.isXeroConnected = false; + this.xeroConnectionInProgress = false; + } else { + this.toastService.displayToastMessage(ToastSeverity.ERROR, errorMessage); + this.router.navigate([`/integrations/xero/onboarding/landing`]); + } + }); + } + + connectXero(companyDetails: DestinationAttribute): void { + this.xeroTenantselected = companyDetails; + this.isContinueDisabled = false; + } + + connectToXero() { + this.xeroConnectionInProgress = true; + const url = `${environment.xero_authorize_uri}?client_id=${environment.xero_oauth_client_id}&scope=${environment.xero_scope}&response_type=code&redirect_uri=${environment.xero_oauth_redirect_uri}&state=xero_local_redirect`; + this.oauthCallbackSubscription = this.helperService.oauthCallbackUrl.subscribe((callbackURL: string) => { + const code = callbackURL.split('code=')[1]?.split('&')[0]; + this.postXeroCredentials(code); + }); + this.helperService.oauthHandler(url); + } + + acceptWarning(data: ConfigurationWarningOut): void { + this.isWarningDialogVisible = false; + if (data.hasAccepted) { + if (data.event === ConfigurationWarningEvent.CLONE_SETTINGS) { + this.router.navigate(['/integrations/xero/onboarding/clone_settings']); + } + } + } + + private constructPayloadAndSave(): void { + if (this.isContinueDisabled) { + return; + } else if (this.isCloneSettingsDisabled) { + this.router.navigate(['/integrations/xero/onboarding/export_settings']); + return; + } + if (this.xeroTenantselected && !this.isContinueDisabled) { + this.xeroConnectionInProgress = true; + this.isContinueDisabled = true; + const tenantMappingPayload: TenantMappingPost = TenantMappingModel.constructPayload(this.xeroTenantselected); + this.xeroConnectorService.postTenantMapping(tenantMappingPayload).subscribe((response:TenantMapping) => { + this.xeroHelperService.refreshXeroDimensions().subscribe(() => { + this.workspaceService.setOnboardingState(XeroOnboardingState.EXPORT_SETTINGS); + this.xeroConnectionInProgress = false; + this.xeroTokenExpired = false; + this.showOrHideDisconnectXero(); + this.isXeroConnected = true; + this.xeroCompanyName = response.tenant_name; + this.checkCloneSettingsAvailablity(); + }); + }); + } else if (!this.isContinueDisabled && this.xeroCompanyName){ + this.checkCloneSettingsAvailablity(); + } + } + + save(): void { + if (this.isContinueDisabled) { + return; + } else if (this.isCloneSettingsDisabled) { + this.constructPayloadAndSave(); + return; + } + + if (!brandingFeatureConfig.featureFlags.cloneSettings) { + this.constructPayloadAndSave(); + } else { + this.checkCloneSettingsAvailablity(); + } + } + + getTenant() { + this.xeroConnectorService.getXeroTenants().subscribe((tenantList: DestinationAttribute[]) => { + this.tenantList = tenantList; + this.isXeroConnected = false; + this.isContinueDisabled = true; + this.xeroConnectionInProgress = false; + this.showOrHideDisconnectXero(); + }); + } + + private showOrHideDisconnectXero(): void { + this.exportSettingService.getExportSettings().subscribe((exportSettings: XeroExportSettingGet) => { + // Do nothing + this.isLoading = false; + + if (!(exportSettings.workspace_general_settings?.reimbursable_expenses_object || exportSettings.workspace_general_settings?.corporate_credit_card_expenses_object)) { + this.showDisconnectXero = true; + } + }, () => { + // Showing Disconnect Xero button since the customer didn't set up the next step + this.showDisconnectXero = true; + this.isLoading = false; + }); + } + + private setupPage() { + this.xeroConnectorService.getTenantMappings().subscribe((tenant: TenantMapping) => { + this.xeroCompanyName = tenant.tenant_name; + this.isXeroConnected = true; + this.isContinueDisabled = false; + this.showOrHideDisconnectXero(); + this.xeroConnectionInProgress = false; + }, + () => { + this.getTenant(); + }); + } ngOnInit(): void { + this.setupPage(); } } diff --git a/src/app/integrations/xero/xero-onboarding/xero-onboarding-done/xero-onboarding-done.component.html b/src/app/integrations/xero/xero-onboarding/xero-onboarding-done/xero-onboarding-done.component.html index 2c41378ad..59345d37f 100644 --- a/src/app/integrations/xero/xero-onboarding/xero-onboarding-done/xero-onboarding-done.component.html +++ b/src/app/integrations/xero/xero-onboarding/xero-onboarding-done/xero-onboarding-done.component.html @@ -1 +1 @@ -

xero-onboarding-done works!

+ diff --git a/src/app/integrations/xero/xero-onboarding/xero-onboarding-done/xero-onboarding-done.component.ts b/src/app/integrations/xero/xero-onboarding/xero-onboarding-done/xero-onboarding-done.component.ts index 3ebe25424..4fdcf517d 100644 --- a/src/app/integrations/xero/xero-onboarding/xero-onboarding-done/xero-onboarding-done.component.ts +++ b/src/app/integrations/xero/xero-onboarding/xero-onboarding-done/xero-onboarding-done.component.ts @@ -1,4 +1,5 @@ import { Component, OnInit } from '@angular/core'; +import { Router } from '@angular/router'; @Component({ selector: 'app-xero-onboarding-done', @@ -7,7 +8,13 @@ import { Component, OnInit } from '@angular/core'; }) export class XeroOnboardingDoneComponent implements OnInit { - constructor() { } + constructor( + private router: Router + ) { } + + navigateToDashboard(): void { + this.router.navigate([`/integrations/xero/main/dashboard`]); + } ngOnInit(): void { } diff --git a/src/app/integrations/xero/xero-onboarding/xero-onboarding-export-settings/xero-onboarding-export-settings.component.html b/src/app/integrations/xero/xero-onboarding/xero-onboarding-export-settings/xero-onboarding-export-settings.component.html index f2da483ce..8d71236de 100644 --- a/src/app/integrations/xero/xero-onboarding/xero-onboarding-export-settings/xero-onboarding-export-settings.component.html +++ b/src/app/integrations/xero/xero-onboarding/xero-onboarding-export-settings/xero-onboarding-export-settings.component.html @@ -1 +1,4 @@ -

xero-onboarding-export-settings works!

+
+ + +
diff --git a/src/app/integrations/xero/xero-onboarding/xero-onboarding-export-settings/xero-onboarding-export-settings.component.ts b/src/app/integrations/xero/xero-onboarding/xero-onboarding-export-settings/xero-onboarding-export-settings.component.ts index 157c772d5..1eb90695a 100644 --- a/src/app/integrations/xero/xero-onboarding/xero-onboarding-export-settings/xero-onboarding-export-settings.component.ts +++ b/src/app/integrations/xero/xero-onboarding/xero-onboarding-export-settings/xero-onboarding-export-settings.component.ts @@ -1,4 +1,8 @@ import { Component, OnInit } from '@angular/core'; +import { brandingContent } from 'src/app/branding/branding-config'; +import { OnboardingStepper } from 'src/app/core/models/misc/onboarding-stepper.model'; +import { XeroOnboardingModel } from 'src/app/core/models/xero/xero-configuration/xero-onboarding.model'; +import { WorkspaceService } from 'src/app/core/services/common/workspace.service'; @Component({ selector: 'app-xero-onboarding-export-settings', @@ -7,7 +11,13 @@ import { Component, OnInit } from '@angular/core'; }) export class XeroOnboardingExportSettingsComponent implements OnInit { - constructor() { } + brandingContent = brandingContent.xero.configuration.exportSetting; + + onboardingSteps: OnboardingStepper[] = new XeroOnboardingModel().getOnboardingSteps(this.brandingContent.stepName, this.workspaceService.getOnboardingState()); + + constructor( + private workspaceService: WorkspaceService + ) { } ngOnInit(): void { } diff --git a/src/app/integrations/xero/xero-onboarding/xero-onboarding-import-settings/xero-onboarding-import-settings.component.html b/src/app/integrations/xero/xero-onboarding/xero-onboarding-import-settings/xero-onboarding-import-settings.component.html index 96c11da89..22c596262 100644 --- a/src/app/integrations/xero/xero-onboarding/xero-onboarding-import-settings/xero-onboarding-import-settings.component.html +++ b/src/app/integrations/xero/xero-onboarding/xero-onboarding-import-settings/xero-onboarding-import-settings.component.html @@ -1 +1,4 @@ -

xero-onboarding-import-settings works!

+
+ + +
diff --git a/src/app/integrations/xero/xero-onboarding/xero-onboarding-import-settings/xero-onboarding-import-settings.component.ts b/src/app/integrations/xero/xero-onboarding/xero-onboarding-import-settings/xero-onboarding-import-settings.component.ts index 27dfc9e63..76a104468 100644 --- a/src/app/integrations/xero/xero-onboarding/xero-onboarding-import-settings/xero-onboarding-import-settings.component.ts +++ b/src/app/integrations/xero/xero-onboarding/xero-onboarding-import-settings/xero-onboarding-import-settings.component.ts @@ -1,4 +1,8 @@ import { Component, OnInit } from '@angular/core'; +import { brandingContent } from 'src/app/branding/branding-config'; +import { OnboardingStepper } from 'src/app/core/models/misc/onboarding-stepper.model'; +import { XeroOnboardingModel } from 'src/app/core/models/xero/xero-configuration/xero-onboarding.model'; +import { WorkspaceService } from 'src/app/core/services/common/workspace.service'; @Component({ selector: 'app-xero-onboarding-import-settings', @@ -7,7 +11,13 @@ import { Component, OnInit } from '@angular/core'; }) export class XeroOnboardingImportSettingsComponent implements OnInit { - constructor() { } + brandingContent = brandingContent.xero.configuration.importSetting; + + onboardingSteps: OnboardingStepper[] = new XeroOnboardingModel().getOnboardingSteps(this.brandingContent.stepName, this.workspaceService.getOnboardingState()); + + constructor( + private workspaceService: WorkspaceService + ) { } ngOnInit(): void { } diff --git a/src/app/integrations/xero/xero-onboarding/xero-onboarding-routing.module.ts b/src/app/integrations/xero/xero-onboarding/xero-onboarding-routing.module.ts index 6daba1cec..97bb488f5 100644 --- a/src/app/integrations/xero/xero-onboarding/xero-onboarding-routing.module.ts +++ b/src/app/integrations/xero/xero-onboarding/xero-onboarding-routing.module.ts @@ -30,17 +30,17 @@ const routes: Routes = [ { path: 'import_settings', component: XeroOnboardingImportSettingsComponent, - canActivate: [XeroTokenGuard, TenantGuard] + canActivate: [XeroTokenGuard] }, { path: 'advanced_settings', component: XeroOnboardingAdvancedSettingsComponent, - canActivate: [XeroTokenGuard, TenantGuard] + canActivate: [XeroTokenGuard] }, { path: 'done', component: XeroOnboardingDoneComponent, - canActivate: [XeroTokenGuard, TenantGuard] + canActivate: [XeroTokenGuard] } ] } diff --git a/src/app/integrations/xero/xero-shared/xero-advanced-settings/xero-advanced-settings.component.html b/src/app/integrations/xero/xero-shared/xero-advanced-settings/xero-advanced-settings.component.html index 0b586be85..c1c09b99e 100644 --- a/src/app/integrations/xero/xero-shared/xero-advanced-settings/xero-advanced-settings.component.html +++ b/src/app/integrations/xero/xero-shared/xero-advanced-settings/xero-advanced-settings.component.html @@ -1 +1,111 @@ -

xero-advanced-settings works!

+
+ +
+
+
+ + +
+
+
+
+ + +
+
+
+ + + +
+ + +
+
+
+ +
+ + +
+
+ + + + + + + + + + + + + + +
+
+ +
+
diff --git a/src/app/integrations/xero/xero-shared/xero-advanced-settings/xero-advanced-settings.component.ts b/src/app/integrations/xero/xero-shared/xero-advanced-settings/xero-advanced-settings.component.ts index 4615d75cf..c4cdcedd7 100644 --- a/src/app/integrations/xero/xero-shared/xero-advanced-settings/xero-advanced-settings.component.ts +++ b/src/app/integrations/xero/xero-shared/xero-advanced-settings/xero-advanced-settings.component.ts @@ -1,4 +1,19 @@ import { Component, OnInit } from '@angular/core'; +import { FormGroup } from '@angular/forms'; +import { Router } from '@angular/router'; +import { forkJoin } from 'rxjs'; +import { brandingConfig, brandingContent, brandingFeatureConfig, brandingKbArticles } from 'src/app/branding/branding-config'; +import { ConditionField, ExpenseFilterResponse } from 'src/app/core/models/common/advanced-settings.model'; +import { EmailOption, SelectFormOption } from 'src/app/core/models/common/select-form-option.model'; +import { DestinationAttribute } from 'src/app/core/models/db/destination-attribute.model'; +import { AppName, ConfigurationCta, ToastSeverity, XeroFyleField, XeroOnboardingState } from 'src/app/core/models/enum/enum.model'; +import { XeroWorkspaceGeneralSetting } from 'src/app/core/models/xero/db/xero-workspace-general-setting.model'; +import { XeroAdvancedSettingGet, XeroAdvancedSettingModel } from 'src/app/core/models/xero/xero-configuration/xero-advanced-settings.model'; +import { IntegrationsToastService } from 'src/app/core/services/common/integrations-toast.service'; +import { MappingService } from 'src/app/core/services/common/mapping.service'; +import { WorkspaceService } from 'src/app/core/services/common/workspace.service'; +import { XeroAdvancedSettingsService } from 'src/app/core/services/xero/xero-configuration/xero-advanced-settings.service'; +import { XeroHelperService } from 'src/app/core/services/xero/xero-core/xero-helper.service'; @Component({ selector: 'app-xero-advanced-settings', @@ -7,9 +22,108 @@ import { Component, OnInit } from '@angular/core'; }) export class XeroAdvancedSettingsComponent implements OnInit { - constructor() { } + appName: AppName = AppName.XERO; + + isLoading: boolean = true; + + isOnboarding: boolean = false; + + supportArticleLink: string = brandingKbArticles.onboardingArticles.XERO.ADVANCED_SETTING; + + advancedSettings: XeroAdvancedSettingGet; + + workspaceGeneralSettings: XeroWorkspaceGeneralSetting; + + billPaymentAccounts: DestinationAttribute[]; + + advancedSettingForm: FormGroup; + + memoStructure: string[] = []; + + brandingConfig = brandingConfig; + + adminEmails: EmailOption[] = []; + + paymentSyncOptions: SelectFormOption[] = XeroAdvancedSettingModel.getPaymentSyncOptions(); + + hours: SelectFormOption[] = [...Array(24).keys()].map(day => { + return { + label: (day + 1).toString(), + value: day + 1 + }; + }); + + ConfigurationCtaText = ConfigurationCta; + + isSaveInProgress: boolean; + + readonly brandingFeatureConfig = brandingFeatureConfig; + + readonly brandingContent = brandingContent.configuration.advancedSettings; + + + constructor( + private advancedSettingService: XeroAdvancedSettingsService, + private router: Router, + private workspaceService: WorkspaceService, + private xeroHelperService: XeroHelperService, + private mappingService: MappingService, + private toastService: IntegrationsToastService + ) { } + + navigateToPreviousStep(): void { + this.router.navigate([`/integrations/xero/onboarding/import_settings`]); + } + + save(): void { + const advancedSettingPayload = XeroAdvancedSettingModel.constructPayload(this.advancedSettingForm); + this.isSaveInProgress = true; + + this.advancedSettingService.postAdvancedSettings(advancedSettingPayload).subscribe(() => { + this.isSaveInProgress = false; + this.toastService.displayToastMessage(ToastSeverity.SUCCESS, 'Advanced settings saved successfully'); + + if (this.isOnboarding) { + this.workspaceService.setOnboardingState(XeroOnboardingState.COMPLETE); + this.router.navigate([`/integrations/xero/onboarding/done`]); + } + }, () => { + this.isSaveInProgress = false; + this.toastService.displayToastMessage(ToastSeverity.ERROR, 'Error saving advanced settings, please try again later'); + }); + } + + refreshDimensions() { + this.xeroHelperService.refreshXeroDimensions().subscribe(); + } + + private setupFormWatchers() { + XeroAdvancedSettingModel.setConfigurationSettingValidatorsAndWatchers(this.advancedSettingForm); + } + + + private setupPage() { + this.isOnboarding = this.router.url.includes('onboarding'); + forkJoin([ + this.advancedSettingService.getAdvancedSettings(), + this.mappingService.getDestinationAttributes(XeroFyleField.BANK_ACCOUNT, 'v1', 'xero'), + this.workspaceService.getWorkspaceGeneralSettings(), + this.advancedSettingService.getWorkspaceAdmins() + ]).subscribe(response => { + this.advancedSettings = response[0]; + this.billPaymentAccounts = response[1]; + this.workspaceGeneralSettings = response[2]; + this.adminEmails = this.advancedSettings.workspace_schedules?.additional_email_options ? this.advancedSettings.workspace_schedules?.additional_email_options.concat(response[3]) : response[3]; + + this.advancedSettingForm = XeroAdvancedSettingModel.mapAPIResponseToFormGroup(this.advancedSettings, this.adminEmails); + + this.setupFormWatchers(); + this.isLoading = false; + }); + } ngOnInit(): void { + this.setupPage(); } } diff --git a/src/app/integrations/xero/xero-shared/xero-export-settings/xero-export-settings.component.html b/src/app/integrations/xero/xero-shared/xero-export-settings/xero-export-settings.component.html index dd1a112dd..e34939645 100644 --- a/src/app/integrations/xero/xero-shared/xero-export-settings/xero-export-settings.component.html +++ b/src/app/integrations/xero/xero-shared/xero-export-settings/xero-export-settings.component.html @@ -1 +1,206 @@ -

xero-export-settings works!

+
+
+ +
+
+
+ + +
+
+
+
+
+ + +
+
+
+ + +
+
+ Note: In case the employee records are not auto matched by the integration, you could still manually map the records from the Mappings section of the integration. +
+
+
+
+ + +
+
+ + +
+ +
+ + +
+ +
+ + +
+
+
+ +
+ + +
+ +
+
+ + +
+ +
+ + +
+ +
+ + +
+ +
+ + +
+ +
+ + +
+
+
+ + +
+
+
+ + + + diff --git a/src/app/integrations/xero/xero-shared/xero-export-settings/xero-export-settings.component.ts b/src/app/integrations/xero/xero-shared/xero-export-settings/xero-export-settings.component.ts index 761b35d1c..314a5f21f 100644 --- a/src/app/integrations/xero/xero-shared/xero-export-settings/xero-export-settings.component.ts +++ b/src/app/integrations/xero/xero-shared/xero-export-settings/xero-export-settings.component.ts @@ -1,4 +1,19 @@ import { Component, OnInit } from '@angular/core'; +import { FormGroup } from '@angular/forms'; +import { Router } from '@angular/router'; +import { forkJoin } from 'rxjs'; +import { brandingConfig, brandingContent, brandingFeatureConfig, brandingKbArticles } from 'src/app/branding/branding-config'; +import { SelectFormOption } from 'src/app/core/models/common/select-form-option.model'; +import { DefaultDestinationAttribute, DestinationAttribute } from 'src/app/core/models/db/destination-attribute.model'; +import { AppName, ConfigurationCta, ConfigurationWarningEvent, EmployeeFieldMapping, ToastSeverity, XeroCorporateCreditCardExpensesObject, XeroOnboardingState, XeroReimbursableExpensesObject } from 'src/app/core/models/enum/enum.model'; +import { ConfigurationWarningOut } from 'src/app/core/models/misc/configuration-warning.model'; +import { XeroExportSettingGet, XeroExportSettingModel } from 'src/app/core/models/xero/xero-configuration/xero-export-settings.model'; +import { HelperService } from 'src/app/core/services/common/helper.service'; +import { IntegrationsToastService } from 'src/app/core/services/common/integrations-toast.service'; +import { MappingService } from 'src/app/core/services/common/mapping.service'; +import { WorkspaceService } from 'src/app/core/services/common/workspace.service'; +import { XeroExportSettingsService } from 'src/app/core/services/xero/xero-configuration/xero-export-settings.service'; +import { XeroHelperService } from 'src/app/core/services/xero/xero-core/xero-helper.service'; @Component({ selector: 'app-xero-export-settings', @@ -7,9 +22,128 @@ import { Component, OnInit } from '@angular/core'; }) export class XeroExportSettingsComponent implements OnInit { - constructor() { } + isLoading: boolean = true; + + redirectLink: string = brandingKbArticles.onboardingArticles.XERO.EXPORT_SETTING; + + brandingConfig = brandingConfig; + + isOnboarding: boolean; + + windowReference: Window; + + exportSettings: XeroExportSettingGet; + + bankAccounts: DestinationAttribute[]; + + reimbursableExportTypes: SelectFormOption[] = XeroExportSettingModel.getReimbursableExportTypes(); + + creditCardExportTypes = XeroExportSettingModel.getCreditCardExportTypes(); + + reimbursableExpenseGroupByOptions = XeroExportSettingModel.getReimbursableExpenseGroupingOptions(); + + cccExpenseGroupByOptions = XeroExportSettingModel.getCCCExpenseGroupingOptions(); + + reimbursableExpenseGroupingDateOptions = XeroExportSettingModel.getReimbursableExpenseGroupingDateOptions(); + + cccExpenseGroupingDateOptions = XeroExportSettingModel.getCCCExpenseGroupingDateOptions(); + + autoMapEmployeeTypes = XeroExportSettingModel.getAutoMapEmployeeOptions(); + + expenseStateOptions = XeroExportSettingModel.getReimbursableExpenseStateOptions(); + + cccExpenseStateOptions = XeroExportSettingModel.getCCCExpenseStateOptions(); + + exportSettingForm: FormGroup; + + isSaveInProgress: boolean; + + isConfirmationDialogVisible: boolean; + + warningDialogText: string; + + appName: AppName = AppName.XERO; + + EmployeeFieldMapping = EmployeeFieldMapping; + + ConfigurationCtaText = ConfigurationCta; + + readonly brandingFeatureConfig = brandingFeatureConfig; + + readonly brandingContent = brandingContent.xero.configuration.exportSetting; + + constructor( + public helperService: HelperService, + private exportSettingService: XeroExportSettingsService, + private mappingService: MappingService, + private xeroHelperService: XeroHelperService, + private router : Router, + private workspaceService: WorkspaceService, + private toastService: IntegrationsToastService + ) { } + + refreshDimensions(isRefresh: boolean) { + this.xeroHelperService.refreshXeroDimensions().subscribe(); + } + + save() { + if (this.exportSettingForm.valid) { + this.constructPayloadAndSave({ + hasAccepted: true, + event: ConfigurationWarningEvent.XERO_EXPORT_SETTINGS + }); + } + } + + constructPayloadAndSave(event: ConfigurationWarningOut) { + if (event.hasAccepted) { + this.isSaveInProgress = true; + const exportSettingPayload = XeroExportSettingModel.constructPayload(this.exportSettingForm); + this.exportSettingService.postExportSettings(exportSettingPayload).subscribe((response: XeroExportSettingGet) => { + this.isSaveInProgress = false; + this.toastService.displayToastMessage(ToastSeverity.SUCCESS, 'Export settings saved successfully'); + + if (this.isOnboarding) { + this.workspaceService.setOnboardingState(XeroOnboardingState.IMPORT_SETTINGS); + this.router.navigate([`/integrations/xero/onboarding/import_settings`]); + } + }, () => { + this.isSaveInProgress = false; + this.toastService.displayToastMessage(ToastSeverity.ERROR, 'Error saving export settings, please try again later'); + }); + } + } + + setupPage() { + this.isOnboarding = this.router.url.includes('onboarding'); + const destinationAttributes = ['BANK_ACCOUNT']; + + forkJoin([ + this.exportSettingService.getExportSettings(), + this.mappingService.getGroupedDestinationAttributes(destinationAttributes, 'v1', 'xero') + ]).subscribe(response => { + this.exportSettings = response[0]; + this.bankAccounts = response[1].BANK_ACCOUNT; + this.exportSettingForm = XeroExportSettingModel.mapAPIResponseToFormGroup(this.exportSettings); + + this.helperService.addExportSettingFormValidator(this.exportSettingForm); + const [exportSettingValidatorRule, exportModuleRule] = XeroExportSettingModel.getValidators(); + + this.helperService.setConfigurationSettingValidatorsAndWatchers(exportSettingValidatorRule, this.exportSettingForm); + + this.helperService.setExportTypeValidatorsAndWatchers(exportModuleRule, this.exportSettingForm); + + this.isLoading = false; + + }); + } + + setupForm() { + throw new Error('Method not implemented.'); + } ngOnInit(): void { + this.setupPage(); } } diff --git a/src/app/integrations/xero/xero-shared/xero-import-settings/xero-import-settings.component.html b/src/app/integrations/xero/xero-shared/xero-import-settings/xero-import-settings.component.html index c2207c283..87df98cbb 100644 --- a/src/app/integrations/xero/xero-shared/xero-import-settings/xero-import-settings.component.html +++ b/src/app/integrations/xero/xero-shared/xero-import-settings/xero-import-settings.component.html @@ -1 +1,120 @@ -

xero-import-settings works!

+
+ +
+
+
+ + +
+
+
+
+
+ + + +
+ + +
+
+ +
+ + +
+ {{brandingContent.notes}} +
+
+ +
+ + +
+ +
+ + +
+ +
+ + + + + +
+
+ + +
+
+
+
+ + +
+
+ + +
diff --git a/src/app/integrations/xero/xero-shared/xero-import-settings/xero-import-settings.component.ts b/src/app/integrations/xero/xero-shared/xero-import-settings/xero-import-settings.component.ts index a0e7dfe9e..fc0fc738a 100644 --- a/src/app/integrations/xero/xero-shared/xero-import-settings/xero-import-settings.component.ts +++ b/src/app/integrations/xero/xero-shared/xero-import-settings/xero-import-settings.component.ts @@ -1,4 +1,21 @@ import { Component, OnInit } from '@angular/core'; +import { AbstractControl, FormArray, FormBuilder, FormGroup, Validators } from '@angular/forms'; +import { Router } from '@angular/router'; +import { forkJoin } from 'rxjs'; +import { brandingConfig, brandingContent, brandingFeatureConfig, brandingKbArticles } from 'src/app/branding/branding-config'; +import { ExpenseField, ImportSettingMappingRow, ImportSettingsModel } from 'src/app/core/models/common/import-settings.model'; +import { DestinationAttribute } from 'src/app/core/models/db/destination-attribute.model'; +import { FyleField, IntegrationField } from 'src/app/core/models/db/mapping.model'; +import { AppName, ConfigurationCta, ToastSeverity, XeroOnboardingState } from 'src/app/core/models/enum/enum.model'; +import { XeroFyleField } from 'src/app/core/models/enum/enum.model'; +import { XeroWorkspaceGeneralSetting } from 'src/app/core/models/xero/db/xero-workspace-general-setting.model'; +import { XeroImportSettingGet, XeroImportSettingModel } from 'src/app/core/models/xero/xero-configuration/xero-import-settings.model'; +import { IntegrationsToastService } from 'src/app/core/services/common/integrations-toast.service'; +import { MappingService } from 'src/app/core/services/common/mapping.service'; +import { WorkspaceService } from 'src/app/core/services/common/workspace.service'; +import { XeroConnectorService } from 'src/app/core/services/xero/xero-configuration/xero-connector.service'; +import { XeroImportSettingsService } from 'src/app/core/services/xero/xero-configuration/xero-import-settings.service'; +import { XeroHelperService } from 'src/app/core/services/xero/xero-core/xero-helper.service'; @Component({ selector: 'app-xero-import-settings', @@ -7,9 +24,270 @@ import { Component, OnInit } from '@angular/core'; }) export class XeroImportSettingsComponent implements OnInit { - constructor() { } + isLoading: boolean = true; + + appName: string = AppName.XERO; + + isOnboarding: boolean; + + importSettings: XeroImportSettingGet; + + fyleExpenseFields: FyleField[]; + + workspaceGeneralSettings: XeroWorkspaceGeneralSetting; + + xeroExpenseFields: IntegrationField[]; + + + taxCodes: DestinationAttribute[]; + + importSettingsForm: FormGroup; + + customFieldType: string; + + customFieldControl: AbstractControl; + + customFieldForm: FormGroup = this.formBuilder.group({ + attribute_type: ['', Validators.required], + display_name: [''], + source_placeholder: ['', Validators.required] + }); + + showCustomFieldDialog: boolean; + + isPreviewDialogVisible: boolean; + + customField: ExpenseField; + + customFieldOption: ExpenseField[] = ImportSettingsModel.getCustomFieldOption(); + + isSaveInProgress: boolean; + + ConfigurationCtaText = ConfigurationCta; + + chartOfAccountTypesList: string[] = XeroImportSettingModel.getChartOfAccountTypesList(); + + isTaxGroupSyncAllowed: boolean; + + isProjectMapped: boolean; + + isCustomerPresent: boolean; + + readonly brandingFeatureConfig = brandingFeatureConfig; + + readonly brandingContent = brandingContent.xero.configuration.importSetting; + + readonly supportArticleLink = brandingKbArticles.onboardingArticles.XERO.IMPORT_SETTING; + + readonly brandingConfig = brandingConfig; + + constructor( + private importSettingService: XeroImportSettingsService, + private workspaceService: WorkspaceService, + private router: Router, + private mappingService: MappingService, + private xeroHelperService: XeroHelperService, + private formBuilder: FormBuilder, + private toastService: IntegrationsToastService, + private xeroConnectorService: XeroConnectorService + ) { } + + closeModel() { + this.customFieldForm.reset(); + this.showCustomFieldDialog = false; + } + + showPreviewDialog(visible: boolean) { + this.isPreviewDialogVisible = visible; + } + + closeDialog() { + this.isPreviewDialogVisible = false; + } + + refreshDimensions() { + this.xeroHelperService.refreshXeroDimensions().subscribe(); + } + + navigateToPreviousStep(): void { + this.router.navigate([`/integrations/qbo/onboarding/export_settings`]); + } + + saveFyleExpenseField(): void { + this.customField = { + attribute_type: this.customFieldForm.value.attribute_type.split(' ').join('_').toUpperCase(), + display_name: this.customFieldForm.value.attribute_type, + source_placeholder: this.customFieldForm.value.source_placeholder, + is_dependent: false + }; + + if (this.customFieldControl) { + this.fyleExpenseFields.pop(); + this.fyleExpenseFields.push(this.customField); + this.fyleExpenseFields.push(this.customFieldOption[0]); + const expenseField = { + source_field: this.customField.attribute_type, + destination_field: this.customFieldControl.value.destination_field, + import_to_fyle: true, + is_custom: true, + source_placeholder: this.customField.source_placeholder + }; + (this.importSettingsForm.get('expenseFields') as FormArray).controls.filter(field => field.value.destination_field === this.customFieldControl.value.destination_field)[0].patchValue(expenseField); + ((this.importSettingsForm.get('expenseFields') as FormArray).controls.filter(field => field.value.destination_field === this.customFieldControl.value.destination_field)[0] as FormGroup).controls.import_to_fyle.disable(); + this.customFieldForm.reset(); + this.showCustomFieldDialog = false; + } + } + + private initializeCustomFieldForm(shouldShowDialog: boolean) { + this.customFieldForm.reset(); + this.showCustomFieldDialog = shouldShowDialog; + } + + private createTaxCodeWatcher(): void { + this.importSettingsForm.controls.taxCode.valueChanges.subscribe((isTaxCodeEnabled) => { + if (isTaxCodeEnabled) { + this.importSettingsForm.controls.defaultTaxCode.setValidators(Validators.required); + } else { + this.importSettingsForm.controls.defaultTaxCode.clearValidators(); + this.importSettingsForm.controls.defaultTaxCode.setValue(null); + } + }); + } + + private createCOAWatcher(): void { + this.importSettingsForm.controls.importCategories.valueChanges.subscribe((isImportCategoriesEnabled) => { + if (!isImportCategoriesEnabled) { + this.importSettingsForm.controls.chartOfAccountTypes.setValue(['Expense']); + } + }); + } + + private expenseFieldWatcher() { + + } + + private setupFormWatchers(): void { + this.createTaxCodeWatcher(); + this.createImportCustomerWatcher(); + this.createCOAWatcher(); + this.expenseFieldWatcher(); + const expenseFieldArray = this.importSettingsForm.get('expenseFields') as FormArray; + expenseFieldArray.controls.forEach((control:any) => { + control.valueChanges.subscribe((value: { source_field: string; destination_field: string; }) => { + if (value.source_field === 'custom_field') { + this.initializeCustomFieldForm(true); + this.customFieldType = ''; + this.customFieldControl = control; + this.customFieldControl.patchValue({ + source_field: '', + destination_field: control.value.destination_field, + import_to_fyle: control.value.import_to_fyle, + is_custom: control.value.is_custom, + source_placeholder: null + }); + } + }); + }); + } + + private constructPayloadAndSave() { + this.isSaveInProgress = true; + const importSettingPayload = XeroImportSettingModel.constructPayload(this.importSettingsForm); + this.importSettingService.postImportSettings(importSettingPayload).subscribe(() => { + this.isSaveInProgress = false; + this.toastService.displayToastMessage(ToastSeverity.SUCCESS, 'Import settings saved successfully'); + + if (this.isOnboarding) { + this.workspaceService.setOnboardingState(XeroOnboardingState.ADVANCED_CONFIGURATION); + this.router.navigate([`/integrations/xero/onboarding/advanced_settings`]); + } + }, () => { + this.isSaveInProgress = false; + this.toastService.displayToastMessage(ToastSeverity.ERROR, 'Error saving import settings, please try again later'); + }); + } + + save(): void { + if (this.importSettingsForm.valid) { + this.constructPayloadAndSave(); + } + } + + private createImportCustomerWatcher(): void { + if (brandingConfig.brandId === 'co') { + const formArray = this.importSettingsForm.get('expenseFields') as FormArray; + const index = formArray.value.findIndex((data:any) => data.destination_field === XeroFyleField.CUSTOMER); + formArray.controls.at(index)?.get('import_to_fyle')?.valueChanges.subscribe((isCustomerImportEnabled) => { + if (isCustomerImportEnabled) { + formArray.controls.at(index)?.get('source_field')?.patchValue(XeroFyleField.PROJECT); + this.importSettingsForm.controls.importCustomers.patchValue(true); + } else { + formArray.controls.at(index)?.get('source_field')?.patchValue('DISABLED_XERO_SOURCE_FIELD'); + this.importSettingsForm.controls.importCustomers.patchValue(false); + } + }); + } else { + this.importSettingsForm.controls.importCustomers.valueChanges.subscribe((isCustomerImportEnabled) => { + if (isCustomerImportEnabled) { + this.fyleExpenseFields = this.fyleExpenseFields.filter((field) => field.attribute_type !== XeroFyleField.PROJECT); + } else { + const fyleField = this.fyleExpenseFields.filter((field) => field.attribute_type === XeroFyleField.PROJECT); + if (fyleField.length === 0) { + this.fyleExpenseFields.pop(); + this.fyleExpenseFields.push({ attribute_type: XeroFyleField.PROJECT, display_name: 'Project', is_dependent: false }); + this.fyleExpenseFields.push(this.customFieldOption[0]); + } + } + }); + } + } + + setupPage() { + this.isOnboarding = this.router.url.includes('onboarding'); + forkJoin([ + this.importSettingService.getImportSettings(), + this.mappingService.getFyleFields('v1'), + this.importSettingService.getXeroField(), + this.mappingService.getDestinationAttributes(XeroFyleField.TAX_CODE, 'v1', 'xero'), + this.workspaceService.getWorkspaceGeneralSettings(), + this.xeroConnectorService.getXeroCredentials(this.workspaceService.getWorkspaceId()) + ]).subscribe(response => { + this.importSettings = response[0]; + this.fyleExpenseFields = response[1]; + this.xeroExpenseFields = response[2]; + this.taxCodes = response[3]; + this.workspaceGeneralSettings = response[4]; + + this.isCustomerPresent = this.xeroExpenseFields.findIndex((data:IntegrationField) => data.attribute_type === XeroFyleField.CUSTOMER) !== -1 ? true : false; + + this.importSettingsForm = XeroImportSettingModel.mapAPIResponseToFormGroup(this.importSettings, this.xeroExpenseFields, this.isCustomerPresent); + + if (response[5] && response[5].country !== 'US') { + this.isTaxGroupSyncAllowed = true; + } + + // This is only for Fyle + if (brandingConfig.brandId !== 'co') { + this.xeroExpenseFields = this.xeroExpenseFields.filter((data) => data.attribute_type !== XeroFyleField.CUSTOMER); + } + + this.isProjectMapped = this.importSettings.mapping_settings.findIndex((data) => data.source_field === XeroFyleField.PROJECT && data.destination_field !== XeroFyleField.CUSTOMER) !== -1 ? true : false; + + this.fyleExpenseFields.push({ attribute_type: 'custom_field', display_name: 'Create a Custom Field', is_dependent: true }); + this.setupFormWatchers(); + this.initializeCustomFieldForm(false); + + this.isLoading = false; + }); + } + + updateCustomerImportAvailability(isMapped: boolean) { + this.isProjectMapped = isMapped; + } ngOnInit(): void { + this.setupPage(); } } diff --git a/src/app/integrations/xero/xero-shared/xero-shared.module.ts b/src/app/integrations/xero/xero-shared/xero-shared.module.ts index 3b75313b4..3cb6c47d9 100644 --- a/src/app/integrations/xero/xero-shared/xero-shared.module.ts +++ b/src/app/integrations/xero/xero-shared/xero-shared.module.ts @@ -4,6 +4,7 @@ import { XeroAdvancedSettingsComponent } from './xero-advanced-settings/xero-adv import { XeroExportSettingsComponent } from './xero-export-settings/xero-export-settings.component'; import { XeroImportSettingsComponent } from './xero-import-settings/xero-import-settings.component'; import { SharedModule } from 'src/app/shared/shared.module'; +import { MultiSelectModule } from 'primeng/multiselect'; @NgModule({ declarations: [ @@ -13,7 +14,8 @@ import { SharedModule } from 'src/app/shared/shared.module'; ], imports: [ CommonModule, - SharedModule + SharedModule, + MultiSelectModule ], exports: [ XeroExportSettingsComponent, diff --git a/src/app/shared/components/configuration/configuration-import-field/configuration-import-field.component.html b/src/app/shared/components/configuration/configuration-import-field/configuration-import-field.component.html index a47007587..3441b6160 100644 --- a/src/app/shared/components/configuration/configuration-import-field/configuration-import-field.component.html +++ b/src/app/shared/components/configuration/configuration-import-field/configuration-import-field.component.html @@ -47,7 +47,7 @@
- + @@ -76,8 +76,8 @@
- +
@@ -90,7 +90,7 @@
+

diff --git a/src/app/shared/components/configuration/configuration-import-field/configuration-import-field.component.ts b/src/app/shared/components/configuration/configuration-import-field/configuration-import-field.component.ts index 0a9c727e1..fee783dcf 100644 --- a/src/app/shared/components/configuration/configuration-import-field/configuration-import-field.component.ts +++ b/src/app/shared/components/configuration/configuration-import-field/configuration-import-field.component.ts @@ -3,7 +3,7 @@ import { AbstractControl, FormArray, FormGroup } from '@angular/forms'; import { brandingConfig, brandingFeatureConfig } from 'src/app/branding/branding-config'; import { ImportDefaultField, ImportSettingMappingRow, ImportSettingsCustomFieldRow, ImportSettingsModel } from 'src/app/core/models/common/import-settings.model'; import { FyleField, IntegrationField } from 'src/app/core/models/db/mapping.model'; -import { AppName, MappingSourceField } from 'src/app/core/models/enum/enum.model'; +import { AppName, MappingSourceField, XeroFyleField } from 'src/app/core/models/enum/enum.model'; import { Sage300DefaultFields, Sage300DependentImportFields, Sage300ImportSettingModel } from 'src/app/core/models/sage300/sage300-configuration/sage300-import-settings.model'; import { MappingSetting } from 'src/app/core/models/intacct/intacct-configuration/import-settings.model'; import { HelperService } from 'src/app/core/services/common/helper.service'; @@ -50,12 +50,16 @@ export class ConfigurationImportFieldComponent implements OnInit { AppName = AppName; + isXeroProjectMapped: boolean; + readonly brandingConfig = brandingConfig; readonly brandingFeatureConfig = brandingFeatureConfig; readonly isAsterikAllowed: boolean = brandingFeatureConfig.isAsterikAllowed; + @Output() xeroProjectMapping:EventEmitter = new EventEmitter(); + constructor( public windowService: WindowService ) { } @@ -105,6 +109,23 @@ export class ConfigurationImportFieldComponent implements OnInit { } else { (this.form.get('expenseFields') as FormArray).at(index)?.get('import_to_fyle')?.setValue(true); } + + if (selectedValue === MappingSourceField.PROJECT && (this.form.get('expenseFields') as FormArray).at(index)?.get('source_field')?.value !== XeroFyleField.CUSTOMER && this.appName === AppName.XERO) { + this.isXeroProjectMapped = true; + this.xeroProjectMapping.emit(this.isXeroProjectMapped); + } else { + this.isXeroProjectMapped = false; + this.xeroProjectMapping.emit(this.isXeroProjectMapped); + } + } + + getOptions(expenseField: AbstractControl): FyleField[]{ + if (expenseField.value.destination_field === 'CUSTOMER' && this.appName === AppName.XERO && !expenseField.value.import_to_fyle) { + return this.filteredFyleFields; + } else if (expenseField.value.source_field === 'CATEGORY') { + return this.fyleFieldOptions; + } + return this.fyleFieldOptions; } removeFilter(expenseField: AbstractControl) { @@ -112,6 +133,8 @@ export class ConfigurationImportFieldComponent implements OnInit { (expenseField as FormGroup).controls.import_to_fyle.patchValue(false); (expenseField as FormGroup).controls.import_to_fyle.enable(); event?.stopPropagation(); + this.isXeroProjectMapped = false; + this.xeroProjectMapping.emit(this.isXeroProjectMapped); } onShowWarningForDependentFields(event: any, formGroup: AbstractControl): void { @@ -132,7 +155,7 @@ export class ConfigurationImportFieldComponent implements OnInit { } ngOnInit(): void { - this.filteredFyleFields = this.fyleFieldOptions.filter(option => option.attribute_type !== 'CATEGORY'); + this.filteredFyleFields = this.appName !== AppName.XERO ? this.fyleFieldOptions.filter(option => option.attribute_type !== 'CATEGORY') : [{ attribute_type: 'DISABLED_XERO_SOURCE_FIELD', display_name: 'Project', is_dependent: false }]; } } diff --git a/src/app/shared/components/configuration/configuration-toggle-field/configuration-toggle-field.component.html b/src/app/shared/components/configuration/configuration-toggle-field/configuration-toggle-field.component.html index 5cbb934a1..1c087c0ed 100644 --- a/src/app/shared/components/configuration/configuration-toggle-field/configuration-toggle-field.component.html +++ b/src/app/shared/components/configuration/configuration-toggle-field/configuration-toggle-field.component.html @@ -22,6 +22,6 @@

- +
diff --git a/src/app/shared/components/configuration/configuration-toggle-field/configuration-toggle-field.component.ts b/src/app/shared/components/configuration/configuration-toggle-field/configuration-toggle-field.component.ts index 4438c1277..924c295e7 100644 --- a/src/app/shared/components/configuration/configuration-toggle-field/configuration-toggle-field.component.ts +++ b/src/app/shared/components/configuration/configuration-toggle-field/configuration-toggle-field.component.ts @@ -1,6 +1,7 @@ import { Component, Input, OnInit } from '@angular/core'; import { FormGroup } from '@angular/forms'; import { brandingConfig, brandingContent, brandingFeatureConfig } from 'src/app/branding/branding-config'; +import { AppName } from 'src/app/core/models/enum/enum.model'; import { WindowService } from 'src/app/core/services/common/window.service'; @Component({ @@ -28,6 +29,12 @@ export class ConfigurationToggleFieldComponent implements OnInit { @Input() hideToggle: boolean = false; + @Input() disabled: boolean = false; + + @Input() appName: string; + + AppName = AppName; + readonly brandingFeatureConfig = brandingFeatureConfig; readonly isAsterikAllowed: boolean = brandingFeatureConfig.isAsterikAllowed; @@ -36,6 +43,8 @@ export class ConfigurationToggleFieldComponent implements OnInit { readonly brandingConfig = brandingConfig; + readonly brandingXeroContent = brandingContent.xero.configuration.importSetting.toggleToastMessage; + constructor( public windowService: WindowService ) { } From f978c30056d504752e26457fa03da3080d115ea2 Mon Sep 17 00:00:00 2001 From: Fyle Date: Mon, 18 Mar 2024 13:11:12 +0530 Subject: [PATCH 5/8] PR comments fix --- src/app/core/guard/tenant.guard.ts | 2 +- .../xero/xero-configuration/xero-connector.service.ts | 2 +- .../xero-onboarding-connector.component.ts | 4 ++-- .../xero-onboarding-landing.component.ts | 1 + 4 files changed, 5 insertions(+), 4 deletions(-) diff --git a/src/app/core/guard/tenant.guard.ts b/src/app/core/guard/tenant.guard.ts index e7ab47dec..890515d7c 100644 --- a/src/app/core/guard/tenant.guard.ts +++ b/src/app/core/guard/tenant.guard.ts @@ -40,7 +40,7 @@ export class TenantGuard implements CanActivate { if (error.status === 404) { globalCacheBusterNotifier.next(); this.toastService.displayToastMessage(ToastSeverity.ERROR, 'Oops! You will need to select a tenant to proceed with the onboarding.'); - return this.router.navigateByUrl('/integrations/xero/onboarding/connector'); + return this.router.navigateByUrl('/integrations/xero/onboarding/landing'); } return throwError(error); }) diff --git a/src/app/core/services/xero/xero-configuration/xero-connector.service.ts b/src/app/core/services/xero/xero-configuration/xero-connector.service.ts index 9df958633..2d7381fa0 100644 --- a/src/app/core/services/xero/xero-configuration/xero-connector.service.ts +++ b/src/app/core/services/xero/xero-configuration/xero-connector.service.ts @@ -50,7 +50,7 @@ export class XeroConnectorService { } getXeroTenants(): Observable { - return this.apiService.get(`/workspaces/${this.workspaceId}/xero/tenants/`, {attribute_type: 'TENANT'}); + return this.apiService.get(`/workspaces/${this.workspaceId}/xero/tenants/`, {attribute_type__exact: 'TENANT'}); } postXeroTenants(): Observable { diff --git a/src/app/integrations/xero/xero-onboarding/xero-onboarding-connector/xero-onboarding-connector.component.ts b/src/app/integrations/xero/xero-onboarding/xero-onboarding-connector/xero-onboarding-connector.component.ts index 2b4a5a2a4..542a7bf4f 100644 --- a/src/app/integrations/xero/xero-onboarding/xero-onboarding-connector/xero-onboarding-connector.component.ts +++ b/src/app/integrations/xero/xero-onboarding/xero-onboarding-connector/xero-onboarding-connector.component.ts @@ -175,11 +175,11 @@ export class XeroOnboardingConnectorComponent implements OnInit { private constructPayloadAndSave(): void { if (this.isContinueDisabled) { return; - } else if (this.isCloneSettingsDisabled) { + } else if (this.isCloneSettingsDisabled && this.xeroCompanyName) { this.router.navigate(['/integrations/xero/onboarding/export_settings']); return; } - if (this.xeroTenantselected && !this.isContinueDisabled) { + if (this.xeroTenantselected && !this.xeroCompanyName) { this.xeroConnectionInProgress = true; this.isContinueDisabled = true; const tenantMappingPayload: TenantMappingPost = TenantMappingModel.constructPayload(this.xeroTenantselected); diff --git a/src/app/integrations/xero/xero-onboarding/xero-onboarding-landing/xero-onboarding-landing.component.ts b/src/app/integrations/xero/xero-onboarding/xero-onboarding-landing/xero-onboarding-landing.component.ts index c20157066..5abd8afe9 100644 --- a/src/app/integrations/xero/xero-onboarding/xero-onboarding-landing/xero-onboarding-landing.component.ts +++ b/src/app/integrations/xero/xero-onboarding/xero-onboarding-landing/xero-onboarding-landing.component.ts @@ -72,6 +72,7 @@ export class XeroOnboardingLandingComponent implements OnInit, OnDestroy { private checkProgressAndRedirect(): void { const onboardingState: XeroOnboardingState = this.workspaceService.getOnboardingState(); if (onboardingState !== XeroOnboardingState.COMPLETE) { + this.xeroConnectorService.postXeroTenants().subscribe(); this.router.navigate(['integrations/xero/onboarding/connector']); } else { this.router.navigate(['integrations/xero/main/dashboard']); From c89cd9a164a7e56c1172af608555a7c02b4b6390 Mon Sep 17 00:00:00 2001 From: Fyle Date: Mon, 18 Mar 2024 13:30:30 +0530 Subject: [PATCH 6/8] PR comments fix --- .../xero-onboarding-landing.component.ts | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/app/integrations/xero/xero-onboarding/xero-onboarding-landing/xero-onboarding-landing.component.ts b/src/app/integrations/xero/xero-onboarding/xero-onboarding-landing/xero-onboarding-landing.component.ts index 5abd8afe9..9bfd84628 100644 --- a/src/app/integrations/xero/xero-onboarding/xero-onboarding-landing/xero-onboarding-landing.component.ts +++ b/src/app/integrations/xero/xero-onboarding/xero-onboarding-landing/xero-onboarding-landing.component.ts @@ -72,8 +72,9 @@ export class XeroOnboardingLandingComponent implements OnInit, OnDestroy { private checkProgressAndRedirect(): void { const onboardingState: XeroOnboardingState = this.workspaceService.getOnboardingState(); if (onboardingState !== XeroOnboardingState.COMPLETE) { - this.xeroConnectorService.postXeroTenants().subscribe(); - this.router.navigate(['integrations/xero/onboarding/connector']); + this.xeroConnectorService.postXeroTenants().subscribe(() => { + this.router.navigate(['integrations/xero/onboarding/connector']); + }); } else { this.router.navigate(['integrations/xero/main/dashboard']); } From 0114be4f9670b8cffd4d2625ee1513a1c0750dc7 Mon Sep 17 00:00:00 2001 From: Dhaarani <55541808+DhaaraniCIT@users.noreply.github.com> Date: Mon, 18 Mar 2024 20:11:18 +0530 Subject: [PATCH 7/8] xero configuration setting (#673) xero configuration setting --- .../xero/db/xero-destination-attribute.model.ts | 11 +++++++++++ .../xero-configuration.component.html | 11 ++++++++++- .../xero-configuration.component.ts | 16 ++++++++++++++++ .../xero-configuration.module.ts | 2 ++ 4 files changed, 39 insertions(+), 1 deletion(-) create mode 100644 src/app/core/models/xero/db/xero-destination-attribute.model.ts diff --git a/src/app/core/models/xero/db/xero-destination-attribute.model.ts b/src/app/core/models/xero/db/xero-destination-attribute.model.ts new file mode 100644 index 000000000..2a150f0a2 --- /dev/null +++ b/src/app/core/models/xero/db/xero-destination-attribute.model.ts @@ -0,0 +1,11 @@ +import { DestinationAttribute, GroupedDestinationAttribute } from "../../db/destination-attribute.model"; + +export type DestinationAttributeDetail = { + email: string; + fully_qualified_name: string; +}; + +export interface XeroDestinationAttributes extends DestinationAttribute { + auto_created: boolean; + detail: DestinationAttributeDetail | null; +} diff --git a/src/app/integrations/xero/xero-main/xero-configuration/xero-configuration.component.html b/src/app/integrations/xero/xero-main/xero-configuration/xero-configuration.component.html index 387638c4b..30cee6ef6 100644 --- a/src/app/integrations/xero/xero-main/xero-configuration/xero-configuration.component.html +++ b/src/app/integrations/xero/xero-main/xero-configuration/xero-configuration.component.html @@ -1 +1,10 @@ -

xero-configuration works!

+
+
+
+
+ +
+
+ +
+
diff --git a/src/app/integrations/xero/xero-main/xero-configuration/xero-configuration.component.ts b/src/app/integrations/xero/xero-main/xero-configuration/xero-configuration.component.ts index e264dfbd1..2f71cdd42 100644 --- a/src/app/integrations/xero/xero-main/xero-configuration/xero-configuration.component.ts +++ b/src/app/integrations/xero/xero-main/xero-configuration/xero-configuration.component.ts @@ -1,4 +1,6 @@ import { Component, OnInit } from '@angular/core'; +import { MenuItem } from 'primeng/api'; +import { brandingConfig, brandingContent, brandingFeatureConfig } from 'src/app/branding/branding-config'; @Component({ selector: 'app-xero-configuration', @@ -7,6 +9,20 @@ import { Component, OnInit } from '@angular/core'; }) export class XeroConfigurationComponent implements OnInit { + readonly brandingContent = brandingContent.xero.configuration; + + modules: MenuItem[] = [ + {label: this.brandingContent.exportSetting.stepName, routerLink: '/integrations/xero/main/configuration/export_settings'}, + {label: this.brandingContent.importSetting.stepName, routerLink: '/integrations/xero/main/configuration/import_settings'}, + {label: this.brandingContent.advancedSettings.stepName, routerLink: '/integrations/xero/main/configuration/advanced_settings'} + ]; + + activeModule: MenuItem = this.modules[0]; + + readonly isGradientAllowed: boolean = brandingFeatureConfig.isGradientAllowed; + + readonly brandingConfig = brandingConfig; + constructor() { } ngOnInit(): void { diff --git a/src/app/integrations/xero/xero-main/xero-configuration/xero-configuration.module.ts b/src/app/integrations/xero/xero-main/xero-configuration/xero-configuration.module.ts index e6059944a..8c01c16d7 100644 --- a/src/app/integrations/xero/xero-main/xero-configuration/xero-configuration.module.ts +++ b/src/app/integrations/xero/xero-main/xero-configuration/xero-configuration.module.ts @@ -4,6 +4,7 @@ import { CommonModule } from '@angular/common'; import { XeroConfigurationRoutingModule } from './xero-configuration-routing.module'; import { XeroConfigurationComponent } from './xero-configuration.component'; import { XeroSharedModule } from '../../xero-shared/xero-shared.module'; +import { SharedModule } from 'src/app/shared/shared.module'; @NgModule({ @@ -13,6 +14,7 @@ import { XeroSharedModule } from '../../xero-shared/xero-shared.module'; imports: [ CommonModule, XeroSharedModule, + SharedModule, XeroConfigurationRoutingModule ] }) From cbc0da2073ae6160e1e247617b463c7190861085 Mon Sep 17 00:00:00 2001 From: Dhaarani <55541808+DhaaraniCIT@users.noreply.github.com> Date: Tue, 19 Mar 2024 10:56:26 +0530 Subject: [PATCH 8/8] clone settings construction (#679) clone settings construction --- src/app/branding/branding-config.ts | 4 +- .../xero-configuration/clone-setting.model.ts | 12 +- .../xero-advanced-settings.model.ts | 2 +- .../xero-import-settings.model.ts | 12 +- .../services/common/clone-setting.service.ts | 5 +- .../core/services/common/helper.service.ts | 14 +- .../xero-clone-settings.component.html | 404 ++++++++++++++++++ .../xero-clone-settings.component.scss | 25 ++ .../xero-clone-settings.component.spec.ts | 23 + .../xero-clone-settings.component.ts | 376 ++++++++++++++++ .../xero-onboarding-routing.module.ts | 6 + .../xero-onboarding/xero-onboarding.module.ts | 4 +- .../xero-export-settings.component.html | 5 +- .../xero-export-settings.component.ts | 4 - .../xero-import-settings.component.ts | 6 - .../input/dropdown/dropdown.component.html | 1 + .../input/dropdown/dropdown.component.ts | 2 + .../input/toggle/toggle.component.html | 2 +- .../input/toggle/toggle.component.ts | 2 + .../clone-setting-field.component.html | 4 +- .../clone-setting-field.component.ts | 2 + src/assets/scss/tab-secondary.scss | 4 +- 22 files changed, 880 insertions(+), 39 deletions(-) create mode 100644 src/app/integrations/xero/xero-onboarding/xero-clone-settings/xero-clone-settings.component.html create mode 100644 src/app/integrations/xero/xero-onboarding/xero-clone-settings/xero-clone-settings.component.scss create mode 100644 src/app/integrations/xero/xero-onboarding/xero-clone-settings/xero-clone-settings.component.spec.ts create mode 100644 src/app/integrations/xero/xero-onboarding/xero-clone-settings/xero-clone-settings.component.ts diff --git a/src/app/branding/branding-config.ts b/src/app/branding/branding-config.ts index d3cde696d..2297000c9 100644 --- a/src/app/branding/branding-config.ts +++ b/src/app/branding/branding-config.ts @@ -276,7 +276,7 @@ const content: ContentConfiguration = { }, exportSetting: { stepName: 'Export settings', - headerText: '', + headerText: ' Export Corporate Card Expenses', contentText: 'Enable this to export non-reimbursable expenses from ' + brandingConfig.brandName + '. If not enabled, any corporate credit card expenses will not be exported to Xero.', corporateCard: { cccExpenseBankAccountSubLabel: 'The selected expense payment type will be added to the corporate credit card expenses exported from ' + brandingConfig.brandName + ' to Xero.', @@ -522,7 +522,7 @@ const content: ContentConfiguration = { }, exportSetting: { stepName: 'Export settings', - headerText: '', + headerText: 'Export Corporate Card Expenses', contentText: 'Enable this to export non-reimbursable expenses from ' + brandingConfig.brandName + '. If not enabled, any corporate credit card expenses will not be exported to Xero.', corporateCard: { cccExpenseBankAccountSubLabel: '', diff --git a/src/app/core/models/xero/xero-configuration/clone-setting.model.ts b/src/app/core/models/xero/xero-configuration/clone-setting.model.ts index 0a5649c26..74701e800 100644 --- a/src/app/core/models/xero/xero-configuration/clone-setting.model.ts +++ b/src/app/core/models/xero/xero-configuration/clone-setting.model.ts @@ -19,17 +19,15 @@ export type XeroCloneSettingPost = { } export class XeroCloneSettingModel { - static constructPayload(XeroCloneSettingsForm: FormGroup, customMappingSettings: MappingSetting[]): XeroCloneSettingPost { - const exportSettingPayload = XeroExportSettingModel.constructPayload(XeroCloneSettingsForm); - const importSettingPayload = XeroImportSettingModel.constructPayload(XeroCloneSettingsForm, customMappingSettings); - const advancedSettingPayload = XeroAdvancedSettingModel.constructPayload(XeroCloneSettingsForm); + static constructPayload(exportSettingForm: FormGroup, importSettingForm: FormGroup, advancedSettingForm: FormGroup): XeroCloneSettingPost { + const exportSettingPayload = XeroExportSettingModel.constructPayload(exportSettingForm); + const importSettingPayload = XeroImportSettingModel.constructPayload(importSettingForm); + const advancedSettingPayload = XeroAdvancedSettingModel.constructPayload(advancedSettingForm); - const XeroCloneSettingPayload: XeroCloneSettingPost = { + return { export_settings: exportSettingPayload, import_settings: importSettingPayload, advanced_settings: advancedSettingPayload }; - - return XeroCloneSettingPayload; } } diff --git a/src/app/core/models/xero/xero-configuration/xero-advanced-settings.model.ts b/src/app/core/models/xero/xero-configuration/xero-advanced-settings.model.ts index c2cd37376..9da0fcb43 100644 --- a/src/app/core/models/xero/xero-configuration/xero-advanced-settings.model.ts +++ b/src/app/core/models/xero/xero-configuration/xero-advanced-settings.model.ts @@ -110,7 +110,7 @@ export class XeroAdvancedSettingModel extends HelperUtility{ billPaymentAccount: new FormControl(advancedSettings.general_mappings.payment_account.id ? advancedSettings.general_mappings.payment_account : null), changeAccountingPeriod: new FormControl(advancedSettings.workspace_general_settings.change_accounting_period), autoCreateVendors: new FormControl(advancedSettings.workspace_general_settings.auto_create_destination_entity), - exportSchedule: new FormControl(advancedSettings.workspace_schedules?.enabled ? advancedSettings.workspace_schedules.interval_hours : false), + exportSchedule: new FormControl(advancedSettings.workspace_schedules?.enabled ? true : false), exportScheduleFrequency: new FormControl(advancedSettings.workspace_schedules?.enabled ? advancedSettings.workspace_schedules.interval_hours : 1), autoCreateMerchantDestinationEntity: new FormControl(advancedSettings.workspace_general_settings.auto_create_merchant_destination_entity ? advancedSettings.workspace_general_settings.auto_create_merchant_destination_entity : false), search: new FormControl(), diff --git a/src/app/core/models/xero/xero-configuration/xero-import-settings.model.ts b/src/app/core/models/xero/xero-configuration/xero-import-settings.model.ts index a4192ef7b..df34b279f 100644 --- a/src/app/core/models/xero/xero-configuration/xero-import-settings.model.ts +++ b/src/app/core/models/xero/xero-configuration/xero-import-settings.model.ts @@ -78,7 +78,7 @@ export class XeroImportSettingModel extends ImportSettingsModel { return new FormGroup({ importCategories: new FormControl(importSettings?.workspace_general_settings.import_categories ?? false), expenseFields: new FormArray(expenseFieldsArray), - chartOfAccountTypes: new FormControl(importSettings?.workspace_general_settings.charts_of_accounts ? importSettings.workspace_general_settings.charts_of_accounts : ['Expense']), + chartOfAccountTypes: new FormControl(importSettings?.workspace_general_settings.charts_of_accounts ? importSettings.workspace_general_settings.charts_of_accounts : ['EXPENSE']), importCustomers: new FormControl(importSettings?.workspace_general_settings.import_customers ?? false), taxCode: new FormControl(importSettings?.workspace_general_settings.import_tax_codes ?? false), importSuppliersAsMerchants: new FormControl(importSettings?.workspace_general_settings.import_suppliers_as_merchants ?? false), @@ -90,14 +90,14 @@ export class XeroImportSettingModel extends ImportSettingsModel { static constructPayload(importSettingsForm: FormGroup): XeroImportSettingPost { const emptyDestinationAttribute = {id: null, name: null}; - const chartOfAccounts = XeroImportSettingModel.formatChartOfAccounts(importSettingsForm.get('chartOfAccountTypes')?.value); + const COA = importSettingsForm.get('chartOfAccountTypes')?.value.map((name: string) => name.toUpperCase()); const expenseFieldArray = importSettingsForm.getRawValue().expenseFields.filter(((data:any) => data.destination_field !== XeroFyleField.CUSTOMER)); const mappingSettings = this.constructMappingSettingPayload(expenseFieldArray); const importSettingPayload: XeroImportSettingPost = { workspace_general_settings: { - import_categories: importSettingsForm.get('chartOfAccount')?.value ?? false, - charts_of_accounts: importSettingsForm.get('chartOfAccount')?.value ? chartOfAccounts : ['Expense'], + import_categories: importSettingsForm.get('importCategories')?.value ?? false, + charts_of_accounts: importSettingsForm.get('chartOfAccountTypes')?.value ? COA : ['EXPENSE'], import_tax_codes: importSettingsForm.get('taxCode')?.value, import_suppliers_as_merchants: importSettingsForm.get('importSuppliersAsMerchants')?.value, import_customers: importSettingsForm.get('importCustomers')?.value ? importSettingsForm.get('importCustomers')?.value : false @@ -109,8 +109,4 @@ export class XeroImportSettingModel extends ImportSettingsModel { }; return importSettingPayload; } - - static formatChartOfAccounts(chartOfAccounts: {enabled: boolean, name: string}[]): string[] { - return chartOfAccounts.filter(chartOfAccount => chartOfAccount.enabled).map(chartOfAccount => chartOfAccount.name.toUpperCase()); - } } diff --git a/src/app/core/services/common/clone-setting.service.ts b/src/app/core/services/common/clone-setting.service.ts index 1b96d326a..5680eb8ed 100644 --- a/src/app/core/services/common/clone-setting.service.ts +++ b/src/app/core/services/common/clone-setting.service.ts @@ -4,6 +4,7 @@ import { ApiService } from './api.service'; import { WorkspaceService } from './workspace.service'; import { CloneSettingExist } from '../../models/common/clone-setting.model'; import { QBOCloneSetting, QBOCloneSettingPost } from '../../models/qbo/qbo-configuration/qbo-clone-setting.model'; +import { XeroCloneSetting, XeroCloneSettingPost } from '../../models/xero/xero-configuration/clone-setting.model'; @Injectable({ providedIn: 'root' @@ -22,11 +23,11 @@ export class CloneSettingService { return this.apiService.get(`/user/clone_settings/exists/`, {}); } - getCloneSettings(): Observable { + getCloneSettings(): Observable { return this.apiService.get(`/v2/workspaces/${this.workspaceId}/clone_settings/`, {}); } - postCloneSettings(cloneSettingsPayload: QBOCloneSettingPost): Observable { + postCloneSettings(cloneSettingsPayload: QBOCloneSettingPost | XeroCloneSettingPost): Observable { return this.apiService.put(`/v2/workspaces/${this.workspaceId}/clone_settings/`, cloneSettingsPayload); } } diff --git a/src/app/core/services/common/helper.service.ts b/src/app/core/services/common/helper.service.ts index 1511bf7f5..b017e6009 100644 --- a/src/app/core/services/common/helper.service.ts +++ b/src/app/core/services/common/helper.service.ts @@ -3,7 +3,7 @@ import { Router } from '@angular/router'; import { ApiService } from './api.service'; import { environment } from 'src/environments/environment'; import { AppUrlMap } from '../../models/integrations/integrations.model'; -import { AppUrl, BusinessCentralExportType, ExpenseState, FyleField, ProgressPhase, Sage300ExportType } from '../../models/enum/enum.model'; +import { AppUrl, BusinessCentralExportType, ExpenseGroupingFieldOption, ExpenseState, FyleField, ProgressPhase, Sage300ExportType, XeroCorporateCreditCardExpensesObject, XeroReimbursableExpensesObject } from '../../models/enum/enum.model'; import { AbstractControl, FormGroup, ValidatorFn, Validators } from '@angular/forms'; import { ExportModuleRule, ExportSettingValidatorRule } from '../../models/sage300/sage300-configuration/sage300-export-setting.model'; import { TitleCasePipe } from '@angular/common'; @@ -85,6 +85,16 @@ export class HelperService { form.controls[controllerName].setValue(Sage300ExportType.PURCHASE_INVOICE); } + setXeroExportTypeControllerValue(form: FormGroup, controllerName: string): void { + if (controllerName === 'reimbursableExportType') { + form.controls[controllerName].patchValue(XeroReimbursableExpensesObject.PURCHASE_BILL); + form.controls.reimbursableExportGroup.patchValue(ExpenseGroupingFieldOption.CLAIM_NUMBER); + } else { + form.controls[controllerName].patchValue(XeroCorporateCreditCardExpensesObject.BANK_TRANSACTION); + form.controls.creditCardExportGroup.patchValue(ExpenseGroupingFieldOption.EXPENSE_ID); + } + } + enableFormField(form: FormGroup, controllerName: string): void { form.controls[controllerName].enable(); } @@ -104,6 +114,8 @@ export class HelperService { const urlSplit = this.router.url.split('/'); if (urlSplit[2] === AppUrl.SAGE300 && (controllerName === 'cccExportType' || controllerName === 'reimbursableExportType')) { this.setSage300ExportTypeControllerValue(form, controllerName); + } else if (urlSplit[2] === AppUrl.XERO && (controllerName === 'creditCardExportType' || controllerName === 'reimbursableExportType')) { + this.setXeroExportTypeControllerValue(form, controllerName); } }); } else { diff --git a/src/app/integrations/xero/xero-onboarding/xero-clone-settings/xero-clone-settings.component.html b/src/app/integrations/xero/xero-onboarding/xero-clone-settings/xero-clone-settings.component.html new file mode 100644 index 000000000..086364e0f --- /dev/null +++ b/src/app/integrations/xero/xero-onboarding/xero-clone-settings/xero-clone-settings.component.html @@ -0,0 +1,404 @@ + +
+ +
+ +
+
+ + +
+
+
+ + +
+
+ +
+ + +
+ +
+ + +
+ +
+ + +
+ +
+ + +
+ +
+ + +
+
+ +
+
+
+ + +
+
+ +
+
+ + + +
+ + +
+
+ +
+ + +
+ +
+ + +
+ +
+ + +
+
+
+ + + +
+
+ + + +
+ + +
+
+ +
+ + +
+ +
+ + +
+ +
+ + + +
+ + +
+
+ + + +
+ + +
+
+ + + +
+ + +
+ +
+
+ +
+

+ Send Error Notification to + +

+ + +
+
+ +
+ + +
+ +
+ + +
+ +
+ + + +
+ + +
+
+ +
+ + +
+
+ + + + + + + + + + +
+
\ No newline at end of file diff --git a/src/app/integrations/xero/xero-onboarding/xero-clone-settings/xero-clone-settings.component.scss b/src/app/integrations/xero/xero-onboarding/xero-clone-settings/xero-clone-settings.component.scss new file mode 100644 index 000000000..c5d9724aa --- /dev/null +++ b/src/app/integrations/xero/xero-onboarding/xero-clone-settings/xero-clone-settings.component.scss @@ -0,0 +1,25 @@ +.clone-setting { + &--field-section { + @apply tw-px-24-px; + } + + &--field { + @apply tw-p-24-px; + } + + &--export-setting-header { + @apply tw-flex tw-justify-between; + } + + &--dependent-field { + @apply tw-pt-24-px; + } +} + +:host ::ng-deep .reimbursableExpense .clone-setting-field--label-text, :host ::ng-deep .creditCardExpense .clone-setting-field--label-text { + @apply tw-text-16-px tw-text-text-primary tw-pl-0 #{!important}; +} + +:host ::ng-deep p-dropdown .p-disabled { + @apply tw-text-input-read-only-text tw-bg-select-disabled-bg tw-border-select-disabled-border tw-border-solid tw-opacity-100 #{!important}; +} diff --git a/src/app/integrations/xero/xero-onboarding/xero-clone-settings/xero-clone-settings.component.spec.ts b/src/app/integrations/xero/xero-onboarding/xero-clone-settings/xero-clone-settings.component.spec.ts new file mode 100644 index 000000000..f56b788bc --- /dev/null +++ b/src/app/integrations/xero/xero-onboarding/xero-clone-settings/xero-clone-settings.component.spec.ts @@ -0,0 +1,23 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { XeroCloneSettingsComponent } from './xero-clone-settings.component'; + +describe('XeroCloneSettingsComponent', () => { + let component: XeroCloneSettingsComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + declarations: [ XeroCloneSettingsComponent ] + }) + .compileComponents(); + + fixture = TestBed.createComponent(XeroCloneSettingsComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/app/integrations/xero/xero-onboarding/xero-clone-settings/xero-clone-settings.component.ts b/src/app/integrations/xero/xero-onboarding/xero-clone-settings/xero-clone-settings.component.ts new file mode 100644 index 000000000..abc3b07d1 --- /dev/null +++ b/src/app/integrations/xero/xero-onboarding/xero-clone-settings/xero-clone-settings.component.ts @@ -0,0 +1,376 @@ +import { Component, OnInit } from '@angular/core'; +import { FormArray, FormBuilder, FormGroup, Validators } from '@angular/forms'; +import { Router } from '@angular/router'; +import { forkJoin } from 'rxjs'; +import { brandingConfig, brandingContent } from 'src/app/branding/branding-config'; +import { ExportSettingModel } from 'src/app/core/models/common/export-settings.model'; +import { ExpenseField, ImportSettingsModel } from 'src/app/core/models/common/import-settings.model'; +import { SelectFormOption } from 'src/app/core/models/common/select-form-option.model'; +import { DefaultDestinationAttribute, DestinationAttribute } from 'src/app/core/models/db/destination-attribute.model'; +import { FyleField, IntegrationField } from 'src/app/core/models/db/mapping.model'; +import { AppName, ConfigurationCta, ConfigurationWarningEvent, InputType, ToastSeverity, XeroFyleField } from 'src/app/core/models/enum/enum.model'; +import { ConfigurationWarningOut } from 'src/app/core/models/misc/configuration-warning.model'; +import { OnboardingStepper } from 'src/app/core/models/misc/onboarding-stepper.model'; +import { EmailOptions } from 'src/app/core/models/qbd/qbd-configuration/advanced-setting.model'; +import { XeroCloneSetting, XeroCloneSettingModel } from 'src/app/core/models/xero/xero-configuration/clone-setting.model'; +import { XeroAdvancedSettingModel } from 'src/app/core/models/xero/xero-configuration/xero-advanced-settings.model'; +import { XeroExportSettingModel } from 'src/app/core/models/xero/xero-configuration/xero-export-settings.model'; +import { XeroImportSettingModel } from 'src/app/core/models/xero/xero-configuration/xero-import-settings.model'; +import { XeroOnboardingModel } from 'src/app/core/models/xero/xero-configuration/xero-onboarding.model'; +import { CloneSettingService } from 'src/app/core/services/common/clone-setting.service'; +import { ConfigurationService } from 'src/app/core/services/common/configuration.service'; +import { HelperService } from 'src/app/core/services/common/helper.service'; +import { IntegrationsToastService } from 'src/app/core/services/common/integrations-toast.service'; +import { MappingService } from 'src/app/core/services/common/mapping.service'; +import { WorkspaceService } from 'src/app/core/services/common/workspace.service'; +import { XeroConnectorService } from 'src/app/core/services/xero/xero-configuration/xero-connector.service'; +import { XeroExportSettingsService } from 'src/app/core/services/xero/xero-configuration/xero-export-settings.service'; +import { XeroImportSettingsService } from 'src/app/core/services/xero/xero-configuration/xero-import-settings.service'; + +@Component({ + selector: 'app-xero-clone-settings', + templateUrl: './xero-clone-settings.component.html', + styleUrls: ['./xero-clone-settings.component.scss'] +}) +export class XeroCloneSettingsComponent implements OnInit { + + isLoading: boolean = true; + + onboardingSteps: OnboardingStepper[] = []; + + brandingConfig = brandingConfig; + + bankAccounts: DefaultDestinationAttribute[]; + + reimbursableExportTypes = XeroExportSettingModel.getReimbursableExportTypes(); + + creditCardExportTypes = XeroExportSettingModel.getCreditCardExportTypes(); + + reimbursableExpenseGroupByOptions = XeroExportSettingModel.getReimbursableExpenseGroupingOptions(); + + cccExpenseGroupByOptions = XeroExportSettingModel.getCCCExpenseGroupingOptions(); + + reimbursableExpenseGroupingDateOptions = XeroExportSettingModel.getReimbursableExpenseGroupingDateOptions(); + + cccExpenseGroupingDateOptions = XeroExportSettingModel.getCCCExpenseGroupingDateOptions(); + + autoMapEmployeeTypes = XeroExportSettingModel.getAutoMapEmployeeOptions(); + + expenseStateOptions = XeroExportSettingModel.getReimbursableExpenseStateOptions(); + + cccExpenseStateOptions = XeroExportSettingModel.getCCCExpenseStateOptions(); + + exportSettingForm: FormGroup; + + xeroFields: IntegrationField[]; + + fyleFields: FyleField[]; + + cloneSetting: XeroCloneSetting; + + appName: AppName = AppName.XERO; + + InputType = InputType; + + customFieldOption: ExpenseField[] = ImportSettingsModel.getCustomFieldOption(); + + chartOfAccountTypesList: string[] = XeroImportSettingModel.getChartOfAccountTypesList().map((name: string) => name[0]+name.substr(1).toLowerCase()); + + isTaxGroupSyncAllowed: boolean; + + isProjectMapped: boolean; + + isCustomerPresent: boolean; + + taxCodes: DefaultDestinationAttribute[]; + + importSettingForm: FormGroup; + + paymentSyncOptions: SelectFormOption[] = XeroAdvancedSettingModel.getPaymentSyncOptions(); + + scheduleIntervalHours: SelectFormOption[] = [...Array(24).keys()].map(day => { + return { + label: (day + 1).toString(), + value: day + 1 + }; + }); + + advancedSettingForm: FormGroup; + + adminEmails: EmailOptions[]; + + billPaymentAccounts: DefaultDestinationAttribute[]; + + isSaveInProgress: boolean; + + ConfigurationCtaText = ConfigurationCta; + + isWarningDialogVisible: boolean; + + warningEvent: ConfigurationWarningEvent; + + warningHeaderText: string; + + warningContextText: string; + + primaryButtonText: string; + + customFieldForm: FormGroup = this.formBuilder.group({ + attribute_type: ['', Validators.required], + display_name: [''], + source_placeholder: ['', Validators.required] + }); + + showCustomFieldDialog: boolean; + + isPreviewDialogVisible: boolean; + + customField: { attribute_type: any; display_name: any; source_placeholder: any; is_dependent: boolean; }; + + customFieldControl: any; + + customFieldType: string; + + brandingContent = brandingContent; + + constructor( + private cloneSettingService: CloneSettingService, + private configurationService: ConfigurationService, + private formBuilder: FormBuilder, + private exportSettingService: XeroExportSettingsService, + public helperService: HelperService, + private mappingService: MappingService, + private xeroConnectorService: XeroConnectorService, + private xeroImportSettingsService: XeroImportSettingsService, + private router: Router, + private toastService: IntegrationsToastService, + private workspaceService: WorkspaceService + ) { } + + resetCloneSetting(): void { + this.warningHeaderText = 'Are you sure?'; + this.warningContextText = `By resetting the configuration, you will be configuring each setting individually from the beginning.

Would you like to continue?`; + this.primaryButtonText = 'Yes'; + this.warningEvent = ConfigurationWarningEvent.RESET_CONFIGURATION; + + this.isWarningDialogVisible = true; + } + + acceptWarning(data: ConfigurationWarningOut): void { + this.isWarningDialogVisible = false; + if (data.hasAccepted) { + this.router.navigate([`/integrations/xero/onboarding/export_settings`]); + } + } + + navigateToPreviousStep(): void { + this.router.navigate([`/integrations/xero/onboarding/connector`]); + } + + closeModel() { + this.customFieldForm.reset(); + this.showCustomFieldDialog = false; + } + + showPreviewDialog(visible: boolean) { + this.isPreviewDialogVisible = visible; + } + + closeDialog() { + this.isPreviewDialogVisible = false; + } + + saveFyleExpenseField(): void { + this.customField = { + attribute_type: this.customFieldForm.value.attribute_type.split(' ').join('_').toUpperCase(), + display_name: this.customFieldForm.value.attribute_type, + source_placeholder: this.customFieldForm.value.source_placeholder, + is_dependent: false + }; + + if (this.customFieldControl) { + this.fyleFields.pop(); + this.fyleFields.push(this.customField); + this.fyleFields.push(this.customFieldOption[0]); + const expenseField = { + source_field: this.customField.attribute_type, + destination_field: this.customFieldControl.value.destination_field, + import_to_fyle: true, + is_custom: true, + source_placeholder: this.customField.source_placeholder + }; + (this.importSettingForm.get('expenseFields') as FormArray).controls.filter(field => field.value.destination_field === this.customFieldControl.value.destination_field)[0].patchValue(expenseField); + ((this.importSettingForm.get('expenseFields') as FormArray).controls.filter(field => field.value.destination_field === this.customFieldControl.value.destination_field)[0] as FormGroup).controls.import_to_fyle.disable(); + this.customFieldForm.reset(); + this.showCustomFieldDialog = false; + } + } + + save(): void { + this.isSaveInProgress = true; + const cloneSettingPayload = XeroCloneSettingModel.constructPayload(this.exportSettingForm, this.importSettingForm, this.advancedSettingForm); + + this.cloneSettingService.postCloneSettings(cloneSettingPayload).subscribe((response) => { + this.isSaveInProgress = false; + this.toastService.displayToastMessage(ToastSeverity.SUCCESS, 'Cloned settings successfully'); + this.router.navigate([`/integrations/xero/onboarding/done`]); + }, () => { + this.isSaveInProgress = false; + this.toastService.displayToastMessage(ToastSeverity.ERROR, 'Failed to clone settings'); + }); + + } + + private setupOnboardingSteps(): void { + const onboardingSteps = new XeroOnboardingModel().getOnboardingSteps('Clone Settings', this.workspaceService.getOnboardingState()); + this.onboardingSteps.push(onboardingSteps[0]); + this.onboardingSteps.push({ + active: false, + completed: false, + step: 'Clone Settings', + icon: 'gear-medium', + route: '/integrations/xero/onboarding/clone_settings', + styleClasses: ['step-name-export--text'] + }); + } + + private initializeCustomFieldForm(shouldShowDialog: boolean) { + this.customFieldForm.reset(); + this.showCustomFieldDialog = shouldShowDialog; + } + + private createTaxCodeWatcher(): void { + this.importSettingForm.controls.taxCode.valueChanges.subscribe((isTaxCodeEnabled) => { + if (isTaxCodeEnabled) { + this.importSettingForm.controls.defaultTaxCode.setValidators(Validators.required); + } else { + this.importSettingForm.controls.defaultTaxCode.clearValidators(); + this.importSettingForm.controls.defaultTaxCode.setValue(null); + } + }); + } + + private createCOAWatcher(): void { + this.importSettingForm.controls.importCategories.valueChanges.subscribe((isImportCategoriesEnabled) => { + if (!isImportCategoriesEnabled) { + this.importSettingForm.controls.chartOfAccountTypes.setValue(['Expense']); + } + }); + } + + private createImportCustomerWatcher(): void { + this.importSettingForm.controls.importCustomers.valueChanges.subscribe((isCustomerImportEnabled) => { + if (isCustomerImportEnabled) { + this.fyleFields = this.fyleFields.filter((field) => field.attribute_type !== XeroFyleField.PROJECT); + } else { + const fyleField = this.fyleFields.filter((field) => field.attribute_type === XeroFyleField.PROJECT); + if (fyleField.length === 0) { + this.fyleFields.pop(); + this.fyleFields.push({ attribute_type: XeroFyleField.PROJECT, display_name: 'Project', is_dependent: false }); + this.fyleFields.push(this.customFieldOption[0]); + } + } + }); + } + + private setupImportSettingFormWatcher(): void { + this.createTaxCodeWatcher(); + this.createCOAWatcher(); + this.createImportCustomerWatcher(); + const expenseFieldArray = this.importSettingForm.get('expenseFields') as FormArray; + expenseFieldArray.controls.forEach((control:any) => { + control.valueChanges.subscribe((value: { source_field: string; destination_field: string; }) => { + if (value.source_field === 'custom_field') { + this.initializeCustomFieldForm(true); + this.customFieldType = ''; + this.customFieldControl = control; + this.customFieldControl.patchValue({ + source_field: '', + destination_field: control.value.destination_field, + import_to_fyle: control.value.import_to_fyle, + is_custom: control.value.is_custom, + source_placeholder: null + }); + } + }); + }); + } + + setupAdvancedSettingFormWatcher() { + XeroAdvancedSettingModel.setConfigurationSettingValidatorsAndWatchers(this.advancedSettingForm); + } + + updateCustomerImportAvailability(isMapped: boolean) { + this.isProjectMapped = isMapped; + } + + private setupPage(): void { + this.setupOnboardingSteps(); + const destinationAttributes = [ + XeroFyleField.TAX_CODE, XeroFyleField.BANK_ACCOUNT + ]; + + forkJoin([ + this.cloneSettingService.getCloneSettings(), + this.mappingService.getGroupedDestinationAttributes(destinationAttributes, 'v1', 'xero'), + this.mappingService.getFyleFields('v1'), + this.xeroConnectorService.getXeroCredentials(this.workspaceService.getWorkspaceId()), + this.configurationService.getAdditionalEmails(), + this.xeroImportSettingsService.getXeroField() + ]).subscribe(([cloneSetting, destinationAttributes, fyleFieldsResponse, xeroCredentials, adminEmails, xeroFields]) => { + this.cloneSetting = cloneSetting; + + // Export Settings + this.bankAccounts = destinationAttributes.BANK_ACCOUNT.map((option: DestinationAttribute) => ExportSettingModel.formatGeneralMappingPayload(option)); + + this.reimbursableExportTypes = XeroExportSettingModel.getReimbursableExportTypes(); + this.exportSettingForm = XeroExportSettingModel.mapAPIResponseToFormGroup(cloneSetting.export_settings); + + this.helperService.addExportSettingFormValidator(this.exportSettingForm); + const [exportSettingValidatorRule, exportModuleRule] = XeroExportSettingModel.getValidators(); + + this.helperService.setConfigurationSettingValidatorsAndWatchers(exportSettingValidatorRule, this.exportSettingForm); + + this.helperService.setExportTypeValidatorsAndWatchers(exportModuleRule, this.exportSettingForm); + + // Import Settings + this.xeroFields = xeroFields; + this.taxCodes = destinationAttributes.TAX_CODE.map((option: DestinationAttribute) => ExportSettingModel.formatGeneralMappingPayload(option)); + + if (xeroCredentials && xeroCredentials.country !== 'US') { + this.isTaxGroupSyncAllowed = true; + } + + this.isCustomerPresent = this.xeroFields.findIndex((data:IntegrationField) => data.attribute_type === XeroFyleField.CUSTOMER) !== -1 ? true : false; + + this.xeroFields = this.xeroFields.filter((data) => data.attribute_type !== XeroFyleField.CUSTOMER); + + cloneSetting.import_settings.workspace_general_settings.charts_of_accounts = cloneSetting.import_settings.workspace_general_settings.charts_of_accounts.map((name: string) => name[0]+name.substr(1).toLowerCase()); + + this.isProjectMapped = cloneSetting.import_settings.mapping_settings.findIndex((data: { source_field: XeroFyleField; destination_field: XeroFyleField; }) => data.source_field === XeroFyleField.PROJECT && data.destination_field !== XeroFyleField.CUSTOMER) !== -1 ? true : false; + + this.importSettingForm = XeroImportSettingModel.mapAPIResponseToFormGroup(cloneSetting.import_settings, this.xeroFields, this.isCustomerPresent); + this.fyleFields = fyleFieldsResponse; + this.fyleFields.push({ attribute_type: 'custom_field', display_name: 'Create a Custom Field', is_dependent: true }); + this.setupImportSettingFormWatcher(); + this.initializeCustomFieldForm(false); + + // Advanced Settings + this.adminEmails = adminEmails; + if (this.cloneSetting.advanced_settings.workspace_schedules?.additional_email_options && this.cloneSetting.advanced_settings.workspace_schedules?.additional_email_options.length > 0) { + this.adminEmails = this.adminEmails.concat(this.cloneSetting.advanced_settings.workspace_schedules?.additional_email_options).flat(); + } + + this.billPaymentAccounts = destinationAttributes.BANK_ACCOUNT.map((option: DestinationAttribute) => ExportSettingModel.formatGeneralMappingPayload(option)); + this.advancedSettingForm = XeroAdvancedSettingModel.mapAPIResponseToFormGroup(this.cloneSetting.advanced_settings, this.adminEmails); + this.setupAdvancedSettingFormWatcher(); + + this.isLoading = false; + }); + } + + ngOnInit(): void { + this.setupPage(); + } + +} diff --git a/src/app/integrations/xero/xero-onboarding/xero-onboarding-routing.module.ts b/src/app/integrations/xero/xero-onboarding/xero-onboarding-routing.module.ts index 97bb488f5..4c9f6ff9b 100644 --- a/src/app/integrations/xero/xero-onboarding/xero-onboarding-routing.module.ts +++ b/src/app/integrations/xero/xero-onboarding/xero-onboarding-routing.module.ts @@ -9,6 +9,7 @@ import { XeroOnboardingLandingComponent } from './xero-onboarding-landing/xero-o import { XeroOnboardingComponent } from './xero-onboarding.component'; import { XeroTokenGuard } from 'src/app/core/guard/xero-token.guard'; import { TenantGuard } from 'src/app/core/guard/tenant.guard'; +import { XeroCloneSettingsComponent } from './xero-clone-settings/xero-clone-settings.component'; const routes: Routes = [ { @@ -41,6 +42,11 @@ const routes: Routes = [ path: 'done', component: XeroOnboardingDoneComponent, canActivate: [XeroTokenGuard] + }, + { + path: 'clone_settings', + component: XeroCloneSettingsComponent, + canActivate: [XeroTokenGuard] } ] } diff --git a/src/app/integrations/xero/xero-onboarding/xero-onboarding.module.ts b/src/app/integrations/xero/xero-onboarding/xero-onboarding.module.ts index 82fbd8468..f9935b5f6 100644 --- a/src/app/integrations/xero/xero-onboarding/xero-onboarding.module.ts +++ b/src/app/integrations/xero/xero-onboarding/xero-onboarding.module.ts @@ -11,6 +11,7 @@ import { XeroOnboardingAdvancedSettingsComponent } from './xero-onboarding-advan import { XeroOnboardingDoneComponent } from './xero-onboarding-done/xero-onboarding-done.component'; import { XeroSharedModule } from '../xero-shared/xero-shared.module'; import { SharedModule } from 'src/app/shared/shared.module'; +import { XeroCloneSettingsComponent } from './xero-clone-settings/xero-clone-settings.component'; @NgModule({ @@ -21,7 +22,8 @@ import { SharedModule } from 'src/app/shared/shared.module'; XeroOnboardingExportSettingsComponent, XeroOnboardingImportSettingsComponent, XeroOnboardingAdvancedSettingsComponent, - XeroOnboardingDoneComponent + XeroOnboardingDoneComponent, + XeroCloneSettingsComponent ], imports: [ CommonModule, diff --git a/src/app/integrations/xero/xero-shared/xero-export-settings/xero-export-settings.component.html b/src/app/integrations/xero/xero-shared/xero-export-settings/xero-export-settings.component.html index e34939645..4805b9d77 100644 --- a/src/app/integrations/xero/xero-shared/xero-export-settings/xero-export-settings.component.html +++ b/src/app/integrations/xero/xero-shared/xero-export-settings/xero-export-settings.component.html @@ -101,14 +101,13 @@
-
+
+ [isSectionHeader]="true">
diff --git a/src/app/integrations/xero/xero-shared/xero-export-settings/xero-export-settings.component.ts b/src/app/integrations/xero/xero-shared/xero-export-settings/xero-export-settings.component.ts index 314a5f21f..f02c8754c 100644 --- a/src/app/integrations/xero/xero-shared/xero-export-settings/xero-export-settings.component.ts +++ b/src/app/integrations/xero/xero-shared/xero-export-settings/xero-export-settings.component.ts @@ -138,10 +138,6 @@ export class XeroExportSettingsComponent implements OnInit { }); } - setupForm() { - throw new Error('Method not implemented.'); - } - ngOnInit(): void { this.setupPage(); } diff --git a/src/app/integrations/xero/xero-shared/xero-import-settings/xero-import-settings.component.ts b/src/app/integrations/xero/xero-shared/xero-import-settings/xero-import-settings.component.ts index fc0fc738a..bdd4710ba 100644 --- a/src/app/integrations/xero/xero-shared/xero-import-settings/xero-import-settings.component.ts +++ b/src/app/integrations/xero/xero-shared/xero-import-settings/xero-import-settings.component.ts @@ -38,7 +38,6 @@ export class XeroImportSettingsComponent implements OnInit { xeroExpenseFields: IntegrationField[]; - taxCodes: DestinationAttribute[]; importSettingsForm: FormGroup; @@ -163,15 +162,10 @@ export class XeroImportSettingsComponent implements OnInit { }); } - private expenseFieldWatcher() { - - } - private setupFormWatchers(): void { this.createTaxCodeWatcher(); this.createImportCustomerWatcher(); this.createCOAWatcher(); - this.expenseFieldWatcher(); const expenseFieldArray = this.importSettingsForm.get('expenseFields') as FormArray; expenseFieldArray.controls.forEach((control:any) => { control.valueChanges.subscribe((value: { source_field: string; destination_field: string; }) => { diff --git a/src/app/shared/components/input/dropdown/dropdown.component.html b/src/app/shared/components/input/dropdown/dropdown.component.html index 613bc17eb..2eafb9c8a 100644 --- a/src/app/shared/components/input/dropdown/dropdown.component.html +++ b/src/app/shared/components/input/dropdown/dropdown.component.html @@ -4,6 +4,7 @@ [ngClass]="additionalClasses ? additionalClasses : ''" [placeholder]="placeholder" [options]="options" + [disabled]="isDisabled" [formControlName]="formControllerName">
- +
diff --git a/src/app/shared/components/input/toggle/toggle.component.ts b/src/app/shared/components/input/toggle/toggle.component.ts index 7f7344e95..7d9f11064 100644 --- a/src/app/shared/components/input/toggle/toggle.component.ts +++ b/src/app/shared/components/input/toggle/toggle.component.ts @@ -12,6 +12,8 @@ export class ToggleComponent { @Input() formControllerName: string; + @Input() isDisabled: boolean; + constructor() { } } diff --git a/src/app/shared/components/onboarding/clone-setting/clone-setting-field/clone-setting-field.component.html b/src/app/shared/components/onboarding/clone-setting/clone-setting-field/clone-setting-field.component.html index 30142dbf5..febfb0120 100644 --- a/src/app/shared/components/onboarding/clone-setting/clone-setting-field/clone-setting-field.component.html +++ b/src/app/shared/components/onboarding/clone-setting/clone-setting-field/clone-setting-field.component.html @@ -1,6 +1,6 @@
- + {{ label }} @@ -12,12 +12,14 @@ [placeholder]="placeholder" [options]="options" [form]="form" + [isDisabled]="isDisabled" [formControllerName]="formControllerName" [displayKey]="dropdownDisplayKey" [additionalClasses]="additionalClasses">