diff --git a/src/app/app.module.ts b/src/app/app.module.ts
index 607694fc..7949b807 100644
--- a/src/app/app.module.ts
+++ b/src/app/app.module.ts
@@ -5,6 +5,7 @@ import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
import { FormsModule, ReactiveFormsModule } from '@angular/forms';
import { JwtHelperService, JWT_OPTIONS } from '@auth0/angular-jwt';
import { MatLegacySnackBarModule as MatSnackBarModule, MAT_LEGACY_SNACK_BAR_DEFAULT_OPTIONS as MAT_SNACK_BAR_DEFAULT_OPTIONS } from '@angular/material/legacy-snack-bar';
+import { MatLegacyDialogModule as MatDialogModule } from '@angular/material/legacy-dialog';
import * as Sentry from '@sentry/angular';
@@ -24,7 +25,8 @@ import { GlobalErrorHandler } from './app.errorhandling';
HttpClientModule,
FormsModule,
ReactiveFormsModule,
- MatSnackBarModule
+ MatSnackBarModule,
+ MatDialogModule,
],
providers: [
{
diff --git a/src/app/auth/auth.module.ts b/src/app/auth/auth.module.ts
index debe9dbb..cd950b33 100644
--- a/src/app/auth/auth.module.ts
+++ b/src/app/auth/auth.module.ts
@@ -2,14 +2,13 @@ import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { FlexLayoutModule } from '@angular/flex-layout';
import { MatLegacyButtonModule as MatButtonModule } from '@angular/material/legacy-button';
-
import { LoginComponent } from './login/login.component';
import { LogoutComponent } from './logout/logout.component';
import { FyleCallbackComponent } from './fyle-callback/fyle-callback.component';
import { AuthComponent } from './auth.component';
import { AuthRoutingModule } from './auth-routing.module';
-import { MatLegacyProgressSpinnerModule as MatProgressSpinnerModule } from '@angular/material/legacy-progress-spinner';
import { SharedLoginComponent } from './shared-login/shared-login.component';
+import { MatLegacyProgressSpinnerModule as MatProgressSpinnerModule } from '@angular/material/legacy-progress-spinner';
@NgModule({
@@ -25,7 +24,8 @@ import { SharedLoginComponent } from './shared-login/shared-login.component';
AuthRoutingModule,
FlexLayoutModule,
MatButtonModule,
- MatProgressSpinnerModule
+ MatProgressSpinnerModule,
+ MatButtonModule
]
})
export class AuthModule { }
diff --git a/src/app/core/core.module.ts b/src/app/core/core.module.ts
index 339a9110..b2ef55d1 100644
--- a/src/app/core/core.module.ts
+++ b/src/app/core/core.module.ts
@@ -1,12 +1,16 @@
import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
-
+import { MatLegacyDialogModule as MatDialogModule } from '@angular/material/legacy-dialog';
+import { EmailMultiSelectComponent } from './email-multi-select/email-multi-select.component';
@NgModule({
- declarations: [],
+ declarations: [
+ EmailMultiSelectComponent
+ ],
imports: [
- CommonModule
+ CommonModule,
+ MatDialogModule
]
})
export class CoreModule { }
diff --git a/src/app/core/email-multi-select/email-multi-select.component.html b/src/app/core/email-multi-select/email-multi-select.component.html
new file mode 100644
index 00000000..5c827ccb
--- /dev/null
+++ b/src/app/core/email-multi-select/email-multi-select.component.html
@@ -0,0 +1,57 @@
+
+
+
+
+
+
+
+
+ {{form.value.emails ? form.value.emails[0] : ''}}
+
+
+
+
+
+
+
1" class="example-additional-selection">
+
+ +{{form.value.emails.length - 1}}
+
+
+
+ 1" [ngClass]="[isCloneSettings ? 'email-multi-select--delele-all-icon-clone-settings' : 'email-multi-select--delele-all-icon']"
+ (click)="delete($event, form.value.emails[0], true)">
+
+
+
+
+
No result
+ found
+
+
+
+ {{option.name}}
+
+
+ {{option.email}}
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/app/core/email-multi-select/email-multi-select.component.scss b/src/app/core/email-multi-select/email-multi-select.component.scss
new file mode 100644
index 00000000..e8ec6cb2
--- /dev/null
+++ b/src/app/core/email-multi-select/email-multi-select.component.scss
@@ -0,0 +1,111 @@
+.email-multi-select {
+ &--email-number {
+ margin-top: 5px;
+ }
+
+ &--delele-all-icon {
+ margin-right: 5px;
+ color: #5a5d72;
+ }
+
+ &--delele-all-icon-clone-settings {
+ margin-right: 25px;
+ color: #5a5d72;
+ }
+
+ &--delele-all-icon img {
+ height: 10px;
+ width: 10px;
+ }
+
+ &--delele-all-icon-clone-settings img {
+ height: 10px;
+ width: 10px;
+ }
+
+ &--selected-email {
+ background-color: #ffffff;
+ border: 1px solid #dfdfe2;
+ border-radius: 12px;
+ padding: 10px;
+ height: 15px;
+ font-size: 12px;
+ margin-top: 2px;
+ }
+
+ &--vertical {
+ border-left: 1px solid #dfdfe2;
+ height: 20px;
+ margin-left: 10px;
+ }
+
+ &--display-email {
+ display: block;
+ max-width: 160px;
+ overflow: hidden;
+ white-space: nowrap;
+ text-overflow: ellipsis;
+ }
+
+ &--bottom-name {
+ font-size: 12px;
+ line-height: 15px;
+ color: #A9ACBC;
+ }
+
+ &--head-name {
+ font-size: 14px;
+ line-height: 18px;
+ color: #414562;
+ flex: none;
+ order: 1;
+ flex-grow: 0;
+ }
+
+ &--no-result {
+ padding: 8px 16px;
+ margin: 10px;
+ }
+}
+
+.configuration {
+ &--field-section {
+ padding: 0px 32px;
+ }
+}
+
+p {
+ margin: 0;
+}
+
+.mat-icon-close {
+ color: #a9acbc;
+ padding-left: 5px;
+ padding-top: 5px;
+}
+
+.mat-icon-close img {
+ height: 10px;
+ width: 10px;
+}
+
+.mat-primary .mat-option.mat-selected:not(.mat-option-disabled) {
+ color: #e91e63;
+}
+
+.multiline-mat-option.mat-option {
+ white-space: normal;
+ line-height: normal;
+ height: auto !important;
+ font-size: none !important;
+}
+
+.mat-option {
+ padding-top: 8px;
+ padding-bottom: 8px;
+ margin: 10px;
+}
+
+.example-additional-selection {
+ margin-left: 10px;
+}
diff --git a/src/app/core/email-multi-select/email-multi-select.component.spec.ts b/src/app/core/email-multi-select/email-multi-select.component.spec.ts
new file mode 100644
index 00000000..b012823c
--- /dev/null
+++ b/src/app/core/email-multi-select/email-multi-select.component.spec.ts
@@ -0,0 +1,61 @@
+import { ComponentFixture, TestBed } from '@angular/core/testing';
+
+import { EmailMultiSelectComponent } from './email-multi-select.component';
+import { NoopAnimationsModule } from '@angular/platform-browser/animations';
+import { Router } from '@angular/router';
+import { SearchPipe } from 'src/app/shared/pipes/search.pipe';
+import { FormBuilder } from '@angular/forms';
+import { MatLegacyDialogModule as MatDialogModule } from '@angular/material/legacy-dialog';
+
+describe('EmailMultiSelectComponent', () => {
+ let component: EmailMultiSelectComponent;
+ let fixture: ComponentFixture;
+ const routerSpy = { navigate: jasmine.createSpy('navigate'), url: '/path' };
+ let router: Router;
+ let formBuilder: FormBuilder;
+
+ beforeEach(async () => {
+ await TestBed.configureTestingModule({
+ imports: [MatDialogModule, NoopAnimationsModule],
+ declarations: [ EmailMultiSelectComponent, SearchPipe ],
+ providers: [
+ FormBuilder,
+ { provide: Router, useValue: routerSpy }
+ ]
+ })
+ .compileComponents();
+ });
+
+ beforeEach(() => {
+ fixture = TestBed.createComponent(EmailMultiSelectComponent);
+ component = fixture.componentInstance;
+ formBuilder = TestBed.inject(FormBuilder);
+ const form = formBuilder.group({
+ searchOption: [],
+ emails: [['fyle@fyle.in', 'integrations@fyle.in']],
+ employeeMapping: [['EMPLOYEE']]
+ });
+ component.form = form;
+ const adminEmails: any[] = [{name: 'fyle', email: 'fyle@fyle.in'}, {name: 'dhaara', email: 'fyle1@fyle.in'}];
+ component.options = adminEmails;
+ component.formControllerName = 'employeeMapping';
+ component.isFieldMandatory = true;
+ component.placeholder = 'Select representation';
+
+ fixture.detectChanges();
+ });
+
+ it('should create', () => {
+ expect(component).toBeTruthy();
+ });
+
+ it('delete function check', () => {
+ const event = new Event("click", undefined);
+ expect(component.delete(event, 'fyle@fyle.in')).toBeUndefined();
+ fixture.detectChanges();
+ expect(component.form.controls.emails.value).toEqual(['integrations@fyle.in']);
+ expect(component.delete(event, 'fyle@fyle.in', true)).toBeUndefined();
+ fixture.detectChanges();
+ expect(component.form.controls.emails.value).toBeNull();
+ });
+});
diff --git a/src/app/core/email-multi-select/email-multi-select.component.ts b/src/app/core/email-multi-select/email-multi-select.component.ts
new file mode 100644
index 00000000..a3a685ea
--- /dev/null
+++ b/src/app/core/email-multi-select/email-multi-select.component.ts
@@ -0,0 +1,53 @@
+import { Component, Input, OnInit } from '@angular/core';
+import { FormGroup } from '@angular/forms';
+import { AdvancedSettingFormOption } from 'src/app/core/models/configuration/advanced-setting.model';
+import { ExportSettingFormOption } from 'src/app/core/models/configuration/export-setting.model';
+import { SimpleSearchPage, SimpleSearchType } from 'src/app/core/models/enum/enum.model';
+import { HelperService } from 'src/app/core/services/core/helper.service';
+
+@Component({
+ selector: 'app-email-multi-select',
+ templateUrl: './email-multi-select.component.html',
+ styleUrls: ['./email-multi-select.component.scss']
+})
+export class EmailMultiSelectComponent implements OnInit {
+
+ @Input() form: FormGroup;
+
+ @Input() options: ExportSettingFormOption[] | AdvancedSettingFormOption[] | any[];
+
+ @Input() placeholder: string;
+
+ @Input() formControllerName: string;
+
+ @Input() isFieldMandatory: boolean;
+
+ @Input() mandatoryErrorListName: string;
+
+ @Input() customErrorMessage: string;
+
+ @Input() isCloneSettings: boolean;
+
+ SimpleSearchPage = SimpleSearchPage;
+
+ SimpleSearchType = SimpleSearchType;
+
+ constructor(
+ public helperService: HelperService
+ ) { }
+
+ delete(event: Event, email: string, deleteAll: boolean = false) {
+ event.preventDefault();
+ event.stopPropagation();
+ if (deleteAll) {
+ this.form.controls.emails.patchValue(null);
+ } else {
+ const emails = this.form.value.emails.filter((value: string) => value !== email);
+ this.form.controls.emails.patchValue(emails);
+ }
+ }
+
+ ngOnInit(): void {
+ }
+
+}
diff --git a/src/app/core/models/configuration/clone-setting.model.spec.ts b/src/app/core/models/configuration/clone-setting.model.spec.ts
new file mode 100644
index 00000000..0274b050
--- /dev/null
+++ b/src/app/core/models/configuration/clone-setting.model.spec.ts
@@ -0,0 +1,159 @@
+import { TestBed } from '@angular/core/testing';
+import { UntypedFormControl, UntypedFormGroup} from '@angular/forms';
+import { AutoMapEmployee, CCCExpenseState, CorporateCreditCardExpensesObject, EmployeeFieldMapping, ExpenseState, MappingDestinationField, MappingSourceField, NameInJournalEntry, ReimbursableExpensesObject } from '../enum/enum.model';
+import { CloneSettingModel, CloneSettingPost } from './clone-setting.model';
+import { ImportSettingModel } from './import-setting.model';
+import { MappingSetting } from '../db/mapping-setting.model';
+describe('CloneSettingModel', () => {
+
+ beforeEach(async () => {
+ await TestBed.configureTestingModule({
+ imports: [UntypedFormGroup],
+ declarations: [ CloneSettingModel ]
+ })
+ .compileComponents();
+ });
+
+ it('Should return CloneSettingModel[]', () => {
+ const expence_Field = [{
+ source_field: 'PROJECT',
+ destination_field: 'CLASS',
+ import_to_fyle: true,
+ is_custom: true,
+ source_placeholder: 'Fyle'
+ }];
+
+ const cloneSettingForm= new UntypedFormGroup({
+ employeeMapping: new UntypedFormControl('EMPLOYEE'),
+ autoMapEmployee: new UntypedFormControl('EMPLOYEE_CODE'),
+ expenseState: new UntypedFormControl('PAID'),
+ cccExpenseState: new UntypedFormControl('PAID'),
+ reimbursableExpense: new UntypedFormControl(true),
+ reimbursableExportType: new UntypedFormControl('BILL'),
+ reimbursableExportGroup: new UntypedFormControl('sample'),
+ reimbursableExportDate: new UntypedFormControl(null),
+ creditCardExpense: new UntypedFormControl(true),
+ creditCardExportType: new UntypedFormControl('BILL'),
+ creditCardExportGroup: new UntypedFormControl('sipper'),
+ creditCardExportDate: new UntypedFormControl(null),
+ bankAccount: new UntypedFormControl({id: '1', name: 'Fyle'}),
+ defaultCCCAccount: new UntypedFormControl({id: '1', name: 'Fyle'}),
+ accountsPayable: new UntypedFormControl({id: '1', name: 'Fyle'}),
+ defaultCreditCardVendor: new UntypedFormControl({id: '1', name: 'Fyle'}),
+ qboExpenseAccount: new UntypedFormControl({id: '1', name: 'Fyle'}),
+ defaultDebitCardAccount: new UntypedFormControl({id: '1', name: 'Fyle'}),
+ searchOption: new UntypedFormControl([]),
+ chartOfAccount: new UntypedFormControl(true),
+ chartOfAccountTypes: new UntypedFormControl([{enabled: true, name: 'expence'}]),
+ expenseFields: new UntypedFormControl(expence_Field),
+ importItems: new UntypedFormControl(true),
+ taxCode: new UntypedFormControl(true),
+ defaultTaxCode: new UntypedFormControl({id: '1', name: 'Fyle'}),
+ importVendorsAsMerchants: new UntypedFormControl(true),
+ paymentSync: new UntypedFormControl(true),
+ billPaymentAccount: new UntypedFormControl({id: '1', name: 'Fyle'}),
+ changeAccountingPeriod: new UntypedFormControl(true),
+ singleCreditLineJE: new UntypedFormControl(true),
+ autoCreateVendors: new UntypedFormControl(true),
+ autoCreateMerchantsAsVendors: new UntypedFormControl(true),
+ exportSchedule: new UntypedFormControl(true),
+ exportScheduleFrequency: new UntypedFormControl(10),
+ memoStructure: new UntypedFormControl(['Fyle']),
+ emails: new UntypedFormControl([]),
+ addedEmail: new UntypedFormControl([]),
+ skipExport: new UntypedFormControl(true)
+ });
+
+ const cloneSettingPayload: CloneSettingPost= {
+ employee_mappings: {
+ workspace_general_settings: {
+ employee_field_mapping: EmployeeFieldMapping.EMPLOYEE,
+ auto_map_employees: AutoMapEmployee.EMPLOYEE_CODE
+ }
+ },
+ import_settings: {
+ workspace_general_settings: {
+ import_categories: true,
+ import_items: true,
+ charts_of_accounts: ImportSettingModel.formatChartOfAccounts([{enabled: true, name: 'expence'}]),
+ import_tax_codes: true,
+ import_vendors_as_merchants: true
+ },
+ general_mappings: {
+ default_tax_code: {id: '1', name: 'Fyle'}
+ },
+ mapping_settings: [{
+ source_field: MappingSourceField.PROJECT,
+ destination_field: MappingDestinationField.CLASS,
+ import_to_fyle: true,
+ is_custom: false,
+ source_placeholder: 'Fyle'
+ },
+ {
+ source_field: MappingSourceField.COST_CENTER,
+ destination_field: MappingDestinationField.CUSTOMER,
+ import_to_fyle: false,
+ is_custom: false,
+ source_placeholder: null
+ }]
+ },
+ export_settings: {
+ expense_group_settings: {
+ expense_state: ExpenseState.PAID,
+ ccc_expense_state: CCCExpenseState.PAID,
+ reimbursable_expense_group_fields: ['sample'],
+ reimbursable_export_date_type: null,
+ corporate_credit_card_expense_group_fields: ['sipper'],
+ ccc_export_date_type: null
+ },
+ workspace_general_settings: {
+ reimbursable_expenses_object: ReimbursableExpensesObject.BILL,
+ corporate_credit_card_expenses_object: CorporateCreditCardExpensesObject.BILL,
+ name_in_journal_entry: NameInJournalEntry.EMPLOYEE
+ },
+ general_mappings: {
+ bank_account: {id: '1', name: 'Fyle'},
+ default_ccc_account: {id: '1', name: 'Fyle'},
+ accounts_payable: {id: '1', name: 'Fyle'},
+ default_ccc_vendor: {id: '1', name: 'Fyle'},
+ qbo_expense_account: {id: '1', name: 'Fyle'},
+ default_debit_card_account: {id: '1', name: 'Fyle'}
+ }
+ },
+ advanced_configurations: {
+ workspace_general_settings: {
+ sync_fyle_to_qbo_payments: false,
+ sync_qbo_to_fyle_payments: false,
+ auto_create_destination_entity: true,
+ auto_create_merchants_as_vendors: true,
+ je_single_credit_line: true,
+ change_accounting_period: true,
+ memo_structure: ['Fyle']
+ },
+ general_mappings: {
+ bill_payment_account: {id: '1', name: 'Fyle'}
+ },
+ workspace_schedules: {
+ enabled: true,
+ interval_hours: 10,
+ emails_selected: [],
+ additional_email_options: []
+ }
+ }
+ };
+
+ const existingMappingSettings: MappingSetting[] = [{
+ id: 21,
+ created_at: new Date(),
+ updated_at: new Date(),
+ workspace: 1,
+ source_field: MappingSourceField.COST_CENTER,
+ destination_field: MappingDestinationField.CUSTOMER,
+ import_to_fyle: false,
+ is_custom: false,
+ source_placeholder: null
+ }];
+
+ expect(CloneSettingModel.constructPayload(cloneSettingForm, existingMappingSettings)).toEqual(cloneSettingPayload);
+ });
+});
diff --git a/src/app/core/models/configuration/clone-setting.model.ts b/src/app/core/models/configuration/clone-setting.model.ts
new file mode 100644
index 00000000..5055b62f
--- /dev/null
+++ b/src/app/core/models/configuration/clone-setting.model.ts
@@ -0,0 +1,45 @@
+import { FormGroup } from "@angular/forms";
+import { AdvancedSettingGet, AdvancedSettingModel, AdvancedSettingPost } from "./advanced-setting.model";
+import { ExportSettingGet, ExportSettingModel, ExportSettingPost } from "./export-setting.model";
+import { ImportSettingGet, ImportSettingModel, ImportSettingPost } from "./import-setting.model";
+import { MappingSetting } from "../db/mapping-setting.model";
+import { EmployeeSettingGet, EmployeeSettingModel, EmployeeSettingPost } from "./employee-setting.model";
+
+export type CloneSetting = {
+ workspace_id: number,
+ export_settings: ExportSettingGet,
+ import_settings: ImportSettingGet,
+ advanced_configurations: AdvancedSettingGet,
+ employee_mappings: EmployeeSettingGet
+}
+
+export type CloneSettingPost = {
+ export_settings: ExportSettingPost,
+ import_settings: ImportSettingPost,
+ advanced_configurations: AdvancedSettingPost,
+ employee_mappings: EmployeeSettingPost
+}
+
+export type CloneSettingExist = {
+ is_available: boolean,
+ workspace_name: string
+}
+
+export class CloneSettingModel {
+ static constructPayload(cloneSettingsForm: FormGroup, customMappingSettings: MappingSetting[]): CloneSettingPost {
+
+ const exportSettingPayload = ExportSettingModel.constructPayload(cloneSettingsForm);
+ const importSettingPayload = ImportSettingModel.constructPayload(cloneSettingsForm, customMappingSettings);
+ const advancedSettingPayload = AdvancedSettingModel.constructPayload(cloneSettingsForm);
+ const employeeMappingPayload = EmployeeSettingModel.constructPayload(cloneSettingsForm);
+
+ const cloneSettingPayload: CloneSettingPost = {
+ export_settings: exportSettingPayload,
+ import_settings: importSettingPayload,
+ advanced_configurations: advancedSettingPayload,
+ employee_mappings: employeeMappingPayload
+ };
+
+ return cloneSettingPayload;
+ }
+}
diff --git a/src/app/core/models/configuration/export-setting.model.ts b/src/app/core/models/configuration/export-setting.model.ts
index ab4bdd1d..b30a00de 100644
--- a/src/app/core/models/configuration/export-setting.model.ts
+++ b/src/app/core/models/configuration/export-setting.model.ts
@@ -1,15 +1,19 @@
import { UntypedFormGroup } from "@angular/forms";
-import { CorporateCreditCardExpensesObject, ExpenseGroupingFieldOption, ExpenseState, CCCExpenseState, ExportDateType, ReimbursableExpensesObject, FyleField, NameInJournalEntry } from "../enum/enum.model";
+import { CorporateCreditCardExpensesObject, ExpenseGroupingFieldOption, ExpenseState, CCCExpenseState, ExportDateType, ReimbursableExpensesObject, NameInJournalEntry } from "../enum/enum.model";
import { ExpenseGroupSettingGet, ExpenseGroupSettingPost } from "../db/expense-group-setting.model";
import { DefaultDestinationAttribute, GeneralMapping } from "../db/general-mapping.model";
import { SelectFormOption } from "../misc/select-form-option.model";
-export type ExportSettingWorkspaceGeneralSetting = {
+export type ExportSettingWorkspaceGeneralSettingPost = {
reimbursable_expenses_object: ReimbursableExpensesObject | null,
corporate_credit_card_expenses_object: CorporateCreditCardExpensesObject | null
name_in_journal_entry: NameInJournalEntry;
}
+export interface ExportSettingWorkspaceGeneralSetting extends ExportSettingWorkspaceGeneralSettingPost {
+ is_simplify_report_closure_enabled: boolean
+}
+
export type ExportSettingGeneralMapping = {
bank_account: DefaultDestinationAttribute,
default_ccc_account: DefaultDestinationAttribute,
@@ -21,7 +25,7 @@ export type ExportSettingGeneralMapping = {
export type ExportSettingPost = {
expense_group_settings: ExpenseGroupSettingPost,
- workspace_general_settings: ExportSettingWorkspaceGeneralSetting,
+ workspace_general_settings: ExportSettingWorkspaceGeneralSettingPost,
general_mappings: ExportSettingGeneralMapping
}
@@ -36,6 +40,10 @@ export interface ExportSettingFormOption extends SelectFormOption {
value: ExpenseState | CCCExpenseState | ReimbursableExpensesObject | CorporateCreditCardExpensesObject | ExpenseGroupingFieldOption | ExportDateType;
}
+export interface NameInJournalEntryOptions extends SelectFormOption {
+ value: NameInJournalEntry
+}
+
export class ExportSettingModel {
static constructPayload(exportSettingsForm: UntypedFormGroup): ExportSettingPost {
const emptyDestinationAttribute = {id: null, name: null};
diff --git a/src/app/core/models/configuration/import-setting.model.ts b/src/app/core/models/configuration/import-setting.model.ts
index 832e47b8..e5d4896c 100644
--- a/src/app/core/models/configuration/import-setting.model.ts
+++ b/src/app/core/models/configuration/import-setting.model.ts
@@ -53,8 +53,9 @@ export interface ImportSettingFormOption extends SelectFormOption {
export class ImportSettingModel {
static constructPayload(importSettingsForm: UntypedFormGroup, customMappingSettings: MappingSetting[]): ImportSettingPost {
+
const emptyDestinationAttribute = {id: null, name: null};
- const employeeSettingPayload: ImportSettingPost = {
+ const importSettingPayload: ImportSettingPost = {
workspace_general_settings: {
import_categories: importSettingsForm.get('chartOfAccount')?.value,
import_items: importSettingsForm.get('importItems')?.value,
@@ -67,7 +68,7 @@ export class ImportSettingModel {
},
mapping_settings: ImportSettingModel.formatMappingSettings(importSettingsForm.get('expenseFields')?.value, customMappingSettings)
};
- return employeeSettingPayload;
+ return importSettingPayload;
}
static formatChartOfAccounts(chartOfAccounts: {enabled: boolean, name: string}[]): string[] {
diff --git a/src/app/core/models/enum/enum.model.ts b/src/app/core/models/enum/enum.model.ts
index 8ed51c82..07ebc46f 100644
--- a/src/app/core/models/enum/enum.model.ts
+++ b/src/app/core/models/enum/enum.model.ts
@@ -36,6 +36,11 @@ export enum ReimbursableExpensesObject {
EXPENSE = 'EXPENSE'
}
+export enum ExportSource {
+ REIMBURSABLE = 'reimbursable',
+ CREDIT_CARD = 'credit card'
+}
+
export enum CorporateCreditCardExpensesObject {
CREDIT_CARD_PURCHASE = 'CREDIT CARD PURCHASE',
BILL = 'BILL',
@@ -182,7 +187,9 @@ export enum ClickEvent {
UNMAPPED_MAPPINGS_FILTER = 'Unmapped Mappings Filter',
MAPPED_MAPPINGS_FILTER = 'Mapped Mappings Filter',
DISCONNECT_QBO = 'Disconnect QBO',
- SYNC_DIMENSION = 'Sync Dimension'
+ SYNC_DIMENSION = 'Sync Dimension',
+ CLONE_SETTINGS_BACK = 'Clone Settings Back',
+ CLONE_SETTINGS_RESET = 'Clone Settings Reset'
}
export enum ProgressPhase {
@@ -197,7 +204,8 @@ export enum OnboardingStep {
EXPORT_SETTINGS = 'Export Settings',
IMPORT_SETTINGS = 'Import Settings',
ADVANCED_SETTINGS = 'Advanced Settings',
- ONBOARDING_DONE = 'Onboarding Done'
+ ONBOARDING_DONE = 'Onboarding Done',
+ CLONE_SETTINGS = 'Clone Settings'
}
export enum UpdateEvent {
diff --git a/src/app/core/models/misc/confirmation-dialog.model.ts b/src/app/core/models/misc/confirmation-dialog.model.ts
index e8911337..7b0f0ebb 100644
--- a/src/app/core/models/misc/confirmation-dialog.model.ts
+++ b/src/app/core/models/misc/confirmation-dialog.model.ts
@@ -2,5 +2,6 @@ export type ConfirmationDialog = {
title: string,
primaryCtaText: string,
contents: string,
- hideSecondaryCTA?: boolean
+ hideSecondaryCTA?: boolean,
+ hideWarningIcon?: boolean
};
diff --git a/src/app/core/models/misc/expense-field.model.ts b/src/app/core/models/misc/expense-field.model.ts
index 818a6f8d..216fa3be 100644
--- a/src/app/core/models/misc/expense-field.model.ts
+++ b/src/app/core/models/misc/expense-field.model.ts
@@ -2,3 +2,12 @@ export type ExpenseField = {
attribute_type: string;
display_name: string;
};
+
+export type ExpenseFieldFormArray = {
+ source_field: string;
+ destination_field: string;
+ import_to_fyle: boolean;
+ disable_import_to_fyle: boolean;
+ source_placeholder: string,
+ addSourceField?: boolean
+};
diff --git a/src/app/core/services/configuration/advanced-setting.service.spec.ts b/src/app/core/services/configuration/advanced-setting.service.spec.ts
index 7d418906..9ea3531d 100644
--- a/src/app/core/services/configuration/advanced-setting.service.spec.ts
+++ b/src/app/core/services/configuration/advanced-setting.service.spec.ts
@@ -1,11 +1,16 @@
import { getTestBed, TestBed } from '@angular/core/testing';
import { AdvancedSettingService } from './advanced-setting.service';
+import { NoopAnimationsModule } from '@angular/platform-browser/animations';
import { HttpClientTestingModule, HttpTestingController } from '@angular/common/http/testing';
import { AdvancedSettingGet, AdvancedSettingPost } from '../../models/configuration/advanced-setting.model';
import { environment } from 'src/environments/environment';
import { WorkspaceScheduleEmailOptions } from '../../models/db/workspace-schedule.model';
import { ExpenseFilterResponse, SkipExport } from '../../models/misc/skip-export.model';
import { JoinOption, Operator } from '../../models/enum/enum.model';
+import { MatLegacyDialogModule as MatDialogModule } from '@angular/material/legacy-dialog';
+import { FormBuilder } from '@angular/forms';
+import { paymentSyncOptions } from 'src/app/shared/components/configuration/advanced-settings/advanced-settings.fixture';
+import { of } from 'rxjs';
describe('AdvancedSettingService', () => {
let service: AdvancedSettingService;
@@ -13,13 +18,21 @@ describe('AdvancedSettingService', () => {
let httpMock: HttpTestingController;
const API_BASE_URL = environment.api_url;
const workspace_id = environment.tests.workspaceId;
+ let formbuilder: FormBuilder;
+ let dialogSpy: jasmine.Spy;
+ const dialogRefSpyObj = jasmine.createSpyObj({ afterClosed: of({hours: 1,
+ schedule_enabled: true,
+ emails_selected: ["fyle@fyle.in"],
+ email_added: {name: "fyle", email: 'fyle@fyle.in'}}), close: null });
+ dialogRefSpyObj.componentInstance = { body: '' };
beforeEach(() => {
TestBed.configureTestingModule({
- imports: [HttpClientTestingModule],
+ imports: [HttpClientTestingModule, MatDialogModule, NoopAnimationsModule],
providers: [AdvancedSettingService]
});
injector = getTestBed();
+ formbuilder = TestBed.inject(FormBuilder);
service = injector.inject(AdvancedSettingService);
httpMock = injector.inject(HttpTestingController);
});
@@ -200,4 +213,23 @@ describe('AdvancedSettingService', () => {
});
req.flush(response);
});
+
+ it('getPaymentSyncOptions function check', () => {
+ const value = service.getPaymentSyncOptions();
+ expect(value).toEqual(paymentSyncOptions);
+ });
+
+ it('getFrequencyIntervals function check', () => {
+ service.getFrequencyIntervals();
+ });
+
+ it('openAddemailDialog function check', () => {
+ const form = formbuilder.group({
+ exportScheduleFrequency: 12,
+ emails: ['test@test.com'],
+ exportSchedule: true,
+ addedEmail: []
+ });
+ expect((service as any).openAddemailDialog(form, [])).toBeUndefined();
+ });
});
diff --git a/src/app/core/services/configuration/advanced-setting.service.ts b/src/app/core/services/configuration/advanced-setting.service.ts
index fe0d58cb..04ba17b4 100644
--- a/src/app/core/services/configuration/advanced-setting.service.ts
+++ b/src/app/core/services/configuration/advanced-setting.service.ts
@@ -1,12 +1,16 @@
import { HttpParams } from '@angular/common/http';
-import { Injectable } from '@angular/core';
+import { Injectable, Output, EventEmitter } from '@angular/core';
import { Observable, Subject } from 'rxjs';
import { Cacheable, CacheBuster } from 'ts-cacheable';
-import { AdvancedSettingGet, AdvancedSettingPost, AdvancedSettingWorkspaceSchedulePost } from '../../models/configuration/advanced-setting.model';
+import { AdvancedSettingFormOption, AdvancedSettingGet, AdvancedSettingPost, AdvancedSettingWorkspaceSchedulePost } from '../../models/configuration/advanced-setting.model';
import { WorkspaceSchedule, WorkspaceScheduleEmailOptions } from '../../models/db/workspace-schedule.model';
import { ExpenseFilterResponse, SkipExport } from '../../models/misc/skip-export.model';
import { ApiService } from '../core/api.service';
import { WorkspaceService } from '../workspace/workspace.service';
+import { PaymentSyncDirection } from '../../models/enum/enum.model';
+import { MatLegacyDialog as MatDialog } from '@angular/material/legacy-dialog';
+import { FormGroup } from '@angular/forms';
+import { AddEmailDialogComponent } from 'src/app/shared/components/configuration/advanced-settings/add-email-dialog/add-email-dialog.component';
const advancedSettingsCache$ = new Subject();
const skipExportCache = new Subject();
@@ -16,9 +20,12 @@ const skipExportCache = new Subject();
})
export class AdvancedSettingService {
+ @Output() patchAdminEmailsEmitter: EventEmitter = new EventEmitter();
+
constructor(
private apiService: ApiService,
- private workspaceService: WorkspaceService
+ private workspaceService: WorkspaceService,
+ private dialog: MatDialog
) { }
@Cacheable({
@@ -59,4 +66,53 @@ export class AdvancedSettingService {
getWorkspaceAdmins(): Observable<[WorkspaceScheduleEmailOptions]> {
return this.apiService.get(`/workspaces/${this.workspaceService.getWorkspaceId()}/admins/`, {});
}
+
+ getPaymentSyncOptions(): AdvancedSettingFormOption[] {
+ return [
+ {
+ label: 'None',
+ value: null
+ },
+ {
+ label: 'Export Fyle ACH Payments to Quickbooks Online',
+ value: PaymentSyncDirection.FYLE_TO_QBO
+ },
+ {
+ label: 'Import Quickbooks Payments into Fyle',
+ value: PaymentSyncDirection.QBO_TO_FYLE
+ }
+ ];
+ }
+
+ getFrequencyIntervals(): AdvancedSettingFormOption[] {
+ return [...Array(24).keys()].map(day => {
+ return {
+ label: (day + 1) === 1 ? (day + 1) + ' Hour' : (day + 1) + ' Hours',
+ value: day + 1
+ };
+ });
+ }
+
+ openAddemailDialog(form: FormGroup, adminEmails: WorkspaceScheduleEmailOptions[]): void {
+ const dialogRef = this.dialog.open(AddEmailDialogComponent, {
+ width: '467px',
+ data: {
+ workspaceId: this.workspaceService.getWorkspaceId(),
+ hours: form.value.exportScheduleFrequency,
+ schedulEnabled: form.value.exportSchedule,
+ selectedEmails: form.value.emails
+ }
+ });
+
+ dialogRef.afterClosed().subscribe((result) => {
+ if (result) {
+ form.controls.exportScheduleFrequency.patchValue(result.hours);
+ form.controls.emails.patchValue(result.emails_selected);
+ form.controls.addedEmail.patchValue(result.email_added);
+
+ const additionalEmails = adminEmails.concat(result.email_added);
+ this.patchAdminEmailsEmitter.emit(additionalEmails);
+ }
+ });
+ }
}
diff --git a/src/app/core/services/configuration/clone-setting.service.spec.ts b/src/app/core/services/configuration/clone-setting.service.spec.ts
new file mode 100644
index 00000000..0084e9c9
--- /dev/null
+++ b/src/app/core/services/configuration/clone-setting.service.spec.ts
@@ -0,0 +1,52 @@
+import { TestBed, getTestBed } from '@angular/core/testing';
+
+import { CloneSettingService } from './clone-setting.service';
+import { HttpClientTestingModule, HttpTestingController } from '@angular/common/http/testing';
+import { mockCloneSettingsGet } from 'src/app/integration/onboarding/clone-settings/clone-settings.fixture';
+import { environment } from 'src/environments/environment';
+import { WorkspaceService } from '../workspace/workspace.service';
+
+describe('CloneSettingService', () => {
+ let service: CloneSettingService;
+ let injector: TestBed;
+ let httpMock: HttpTestingController;
+ const API_BASE_URL = environment.api_url;
+ const workspace_id = environment.tests.workspaceId;
+
+ beforeEach(() => {
+ TestBed.configureTestingModule({
+ imports: [
+ HttpClientTestingModule
+ ]
+ });
+ injector = getTestBed();
+ service = TestBed.inject(CloneSettingService);
+ httpMock = injector.inject(HttpTestingController);
+ });
+
+ it('should be created', () => {
+ expect(service).toBeTruthy();
+ });
+
+ it('should get Clone Settings', () => {
+ service.getCloneSettings().subscribe(value => {
+ expect(value).toEqual(mockCloneSettingsGet);
+ });
+ const req = httpMock.expectOne({
+ method: 'GET',
+ url: `${API_BASE_URL}/v2/workspaces/${workspace_id}/clone_settings/`
+ });
+ req.flush(mockCloneSettingsGet);
+ });
+
+ it('should post Clone Settings', () => {
+ service.postCloneSettings(mockCloneSettingsGet).subscribe(value => {
+ expect(value).toEqual(mockCloneSettingsGet);
+ });
+ const req = httpMock.expectOne({
+ method: 'PUT',
+ url: `${API_BASE_URL}/v2/workspaces/${workspace_id}/clone_settings/`
+ });
+ req.flush(mockCloneSettingsGet);
+ });
+});
diff --git a/src/app/core/services/configuration/clone-setting.service.ts b/src/app/core/services/configuration/clone-setting.service.ts
new file mode 100644
index 00000000..df8f4d8e
--- /dev/null
+++ b/src/app/core/services/configuration/clone-setting.service.ts
@@ -0,0 +1,32 @@
+import { Injectable } from '@angular/core';
+import { Observable } from 'rxjs';
+import { CloneSetting, CloneSettingExist, CloneSettingPost } from '../../models/configuration/clone-setting.model';
+import { ApiService } from '../core/api.service';
+import { WorkspaceService } from '../workspace/workspace.service';
+import { FormControl, FormGroup } from '@angular/forms';
+
+@Injectable({
+ providedIn: 'root'
+})
+
+export class CloneSettingService {
+
+ workspaceId = this.workspaceService.getWorkspaceId();
+
+ constructor(
+ private apiService: ApiService,
+ private workspaceService: WorkspaceService
+ ) { }
+
+ checkCloneSettingsExists(): Observable {
+ return this.apiService.get(`/user/clone_settings/exists/`, {});
+ }
+
+ getCloneSettings(): Observable {
+ return this.apiService.get(`/v2/workspaces/${this.workspaceId}/clone_settings/`, {});
+ }
+
+ postCloneSettings(cloneSettingsPayload: CloneSettingPost): Observable {
+ return this.apiService.put(`/v2/workspaces/${this.workspaceId}/clone_settings/`, cloneSettingsPayload);
+ }
+}
diff --git a/src/app/core/services/configuration/employee-setting.service.ts b/src/app/core/services/configuration/employee-setting.service.ts
index 1f2c7585..432893b7 100644
--- a/src/app/core/services/configuration/employee-setting.service.ts
+++ b/src/app/core/services/configuration/employee-setting.service.ts
@@ -1,9 +1,10 @@
import { Injectable } from '@angular/core';
import { Observable, Subject } from 'rxjs';
import { Cacheable, CacheBuster } from 'ts-cacheable';
-import { EmployeeSettingGet, EmployeeSettingPost } from '../../models/configuration/employee-setting.model';
+import { EmployeeSettingFormOption, EmployeeSettingGet, EmployeeSettingPost } from '../../models/configuration/employee-setting.model';
import { ApiService } from '../core/api.service';
import { WorkspaceService } from '../workspace/workspace.service';
+import { AutoMapEmployee, EmployeeFieldMapping } from '../../models/enum/enum.model';
const employeeSettingsCache$ = new Subject();
@@ -32,4 +33,38 @@ export class EmployeeSettingService {
postEmployeeSettings(employeeSettingsPayload: EmployeeSettingPost): Observable {
return this.apiService.put(`/v2/workspaces/${this.workspaceService.getWorkspaceId()}/map_employees/`, employeeSettingsPayload);
}
+
+ getEmployeeFieldMappingOptions(): EmployeeSettingFormOption[] {
+ return [
+ {
+ label: 'Employees',
+ value: EmployeeFieldMapping.EMPLOYEE
+ },
+ {
+ label: 'Vendor',
+ value: EmployeeFieldMapping.VENDOR
+ }
+ ];
+ }
+
+ getAutoMapEmployeeOptions(): EmployeeSettingFormOption[] {
+ return [
+ {
+ value: null,
+ label: 'None'
+ },
+ {
+ value: AutoMapEmployee.NAME,
+ label: 'Fyle Name to QuickBooks Online Display name'
+ },
+ {
+ value: AutoMapEmployee.EMAIL,
+ label: 'Fyle Email to QuickBooks Online Email'
+ },
+ {
+ value: AutoMapEmployee.EMPLOYEE_CODE,
+ label: 'Fyle Employee Code to QuickBooks Online Display name'
+ }
+ ];
+ }
}
diff --git a/src/app/core/services/configuration/export-setting.service.spec.ts b/src/app/core/services/configuration/export-setting.service.spec.ts
index 30b019b1..752dd799 100644
--- a/src/app/core/services/configuration/export-setting.service.spec.ts
+++ b/src/app/core/services/configuration/export-setting.service.spec.ts
@@ -1,9 +1,11 @@
import { getTestBed, TestBed } from '@angular/core/testing';
import { ExportSettingService } from './export-setting.service';
import { ExportSettingGet, ExportSettingPost } from '../../models/configuration/export-setting.model';
-import { ExpenseState, CCCExpenseState, ReimbursableExpensesObject, CorporateCreditCardExpensesObject, ExportDateType, NameInJournalEntry } from '../../models/enum/enum.model';
+import { ExpenseState, CCCExpenseState, ReimbursableExpensesObject, CorporateCreditCardExpensesObject, ExportDateType, ExpenseGroupingFieldOption, NameInJournalEntry } from '../../models/enum/enum.model';
import { HttpTestingController, HttpClientTestingModule } from '@angular/common/http/testing';
import { environment } from 'src/environments/environment';
+import { AbstractControl, FormBuilder, UntypedFormControl, UntypedFormGroup, Validators } from '@angular/forms';
+import { exportResponse, mockCCCExpenseStateOptions, mockReimbursableExpenseGroupingFieldOptions, mockReimbursableExportTypeOptions } from 'src/app/shared/components/configuration/export-settings/export-settings.fixture';
describe('ExportSettingService', () => {
let service: ExportSettingService;
@@ -11,6 +13,8 @@ describe('ExportSettingService', () => {
let httpMock: HttpTestingController;
const API_BASE_URL = environment.api_url;
const workspace_id = environment.tests.workspaceId;
+ let formbuilder: FormBuilder;
+
beforeEach(() => {
TestBed.configureTestingModule({
@@ -18,6 +22,7 @@ describe('ExportSettingService', () => {
providers: [ExportSettingService]
});
injector = getTestBed();
+ formbuilder = TestBed.inject(FormBuilder);
service = injector.inject(ExportSettingService);
httpMock = injector.inject(HttpTestingController);
});
@@ -40,6 +45,7 @@ describe('ExportSettingService', () => {
workspace_general_settings: {
reimbursable_expenses_object: ReimbursableExpensesObject.BILL,
corporate_credit_card_expenses_object: CorporateCreditCardExpensesObject.BILL,
+ is_simplify_report_closure_enabled: true,
name_in_journal_entry: NameInJournalEntry.EMPLOYEE
},
general_mappings: {
@@ -99,6 +105,7 @@ describe('ExportSettingService', () => {
workspace_general_settings: {
reimbursable_expenses_object: ReimbursableExpensesObject.BILL,
corporate_credit_card_expenses_object: CorporateCreditCardExpensesObject.BILL,
+ is_simplify_report_closure_enabled: true,
name_in_journal_entry: NameInJournalEntry.MERCHANT
},
general_mappings: {
@@ -122,4 +129,109 @@ describe('ExportSettingService', () => {
});
+ it('exportSelectionValidator function check', () => {
+ const control = { value: ExpenseState.PAID, parent: formbuilder.group({
+ reimbursableExpense: ReimbursableExpensesObject.BILL
+ }) };
+ expect((service as any).exportSelectionValidator()(control as AbstractControl)).toEqual({forbiddenOption: { value: 'PAID' }});
+ const control1 = { value: ExpenseState.PAYMENT_PROCESSING, parent: formbuilder.group({
+ creditCardExpense: CorporateCreditCardExpensesObject.CREDIT_CARD_PURCHASE
+ }) };
+ expect((service as any).exportSelectionValidator()(control1 as AbstractControl)).toEqual({forbiddenOption: { value: 'PAYMENT_PROCESSING' }});
+ });
+
+ it('createReimbursableExpenseWatcher function check', () => {
+ const form = formbuilder.group({
+ reimbursableExpense: false,
+ expenseState: ExpenseState.PAID,
+ reimbursableExportType: ReimbursableExpensesObject.BILL,
+ reimbursableExportDate: ExportDateType.APPROVED_AT,
+ reimbursableExportGroup: ExpenseGroupingFieldOption.EXPENSE_ID
+ });
+
+ expect((service as any).createReimbursableExpenseWatcher(form, exportResponse)).toBeUndefined();
+
+ form.controls.reimbursableExpense.patchValue(true);
+ expect((service as any).createReimbursableExpenseWatcher(form, exportResponse)).toBeUndefined();
+
+ form.controls.reimbursableExpense.patchValue(false);
+ expect((service as any).createReimbursableExpenseWatcher(form, exportResponse)).toBeUndefined();
+ });
+
+ it('createCreditCardExpenseWatcher function check', () => {
+ const form = formbuilder.group({
+ creditCardExpense: false,
+ cccExpenseState: ExpenseState.PAID,
+ creditCardExportType: CorporateCreditCardExpensesObject.CREDIT_CARD_PURCHASE,
+ creditCardExportGroup: ExpenseGroupingFieldOption.EXPENSE_ID,
+ creditCardExportDate: ExportDateType.POSTED_AT
+ });
+
+ expect((service as any).createCreditCardExpenseWatcher(form, exportResponse)).toBeUndefined();
+
+ form.controls.creditCardExpense.patchValue(true);
+ expect((service as any).createCreditCardExpenseWatcher(form, exportResponse)).toBeUndefined();
+
+ form.controls.creditCardExpense.patchValue(false);
+ expect((service as any).createCreditCardExpenseWatcher(form, exportResponse)).toBeUndefined();
+ });
+
+ it('setGeneralMappingsValidator function check', () => {
+ const form= new UntypedFormGroup({
+ employeeMapping: new UntypedFormControl('EMPLOYEE'),
+ autoMapEmployee: new UntypedFormControl('EMPLOYEE_CODE'),
+ expenseState: new UntypedFormControl('PAID'),
+ cccExpenseState: new UntypedFormControl('PAID'),
+ reimbursableExpense: new UntypedFormControl(true),
+ reimbursableExportType: new UntypedFormControl('BILL'),
+ reimbursableExportGroup: new UntypedFormControl('sample'),
+ reimbursableExportDate: new UntypedFormControl(null),
+ creditCardExpense: new UntypedFormControl(true),
+ creditCardExportType: new UntypedFormControl('BILL'),
+ creditCardExportGroup: new UntypedFormControl('sipper'),
+ creditCardExportDate: new UntypedFormControl(null),
+ bankAccount: new UntypedFormControl({id: '1', name: 'Fyle'}),
+ defaultCCCAccount: new UntypedFormControl({id: '1', name: 'Fyle'}),
+ accountsPayable: new UntypedFormControl({id: '1', name: 'Fyle'}),
+ defaultCreditCardVendor: new UntypedFormControl({id: '1', name: 'Fyle'}),
+ qboExpenseAccount: new UntypedFormControl({id: '1', name: 'Fyle'}),
+ defaultDebitCardAccount: new UntypedFormControl({id: '1', name: 'Fyle'}),
+ searchOption: new UntypedFormControl([]),
+ chartOfAccount: new UntypedFormControl(true),
+ chartOfAccountTypes: new UntypedFormControl([{enabled: true, name: 'expence'}]),
+ importItems: new UntypedFormControl(true),
+ taxCode: new UntypedFormControl(true),
+ defaultTaxCode: new UntypedFormControl({id: '1', name: 'Fyle'}),
+ importVendorsAsMerchants: new UntypedFormControl(true),
+ paymentSync: new UntypedFormControl(true),
+ billPaymentAccount: new UntypedFormControl({id: '1', name: 'Fyle'}),
+ changeAccountingPeriod: new UntypedFormControl(true),
+ singleCreditLineJE: new UntypedFormControl(true),
+ autoCreateVendors: new UntypedFormControl(true),
+ autoCreateMerchantsAsVendors: new UntypedFormControl(true),
+ exportSchedule: new UntypedFormControl(true),
+ exportScheduleFrequency: new UntypedFormControl(10),
+ memoStructure: new UntypedFormControl(['Fyle']),
+ emails: new UntypedFormControl([]),
+ addedEmail: new UntypedFormControl([]),
+ skipExport: new UntypedFormControl(true)
+ });
+ form.controls.creditCardExportType.patchValue(CorporateCreditCardExpensesObject.DEBIT_CARD_EXPENSE);
+ expect((service as any).setGeneralMappingsValidator(form)).toBeUndefined();
+ expect((service as any).showCCCAccountsPayableField(form));
+
+ });
+
+ it('function check', () => {
+ expect((service as any).getExportGroup([ExpenseGroupingFieldOption.EXPENSE_ID])).toEqual('expense_id');
+ expect((service as any).getExportGroup(null)).toEqual('');
+
+ expect((service as any).getReimbursableExpenseGroupingFieldOptions());
+ expect((service as any).getReimbursableExportTypeOptions());
+ expect((service as any).getcreditCardExportTypes());
+ expect((service as any).getReimbursableExpenseGroupingDateOptions());
+ expect((service as any).getReimbursableExpenseStateOptions());
+ expect((service as any).getCCCExpenseStateOptions());
+ expect((service as any).nameInJournalOptions());
+ });
});
diff --git a/src/app/core/services/configuration/export-setting.service.ts b/src/app/core/services/configuration/export-setting.service.ts
index 4508aed8..04cdf805 100644
--- a/src/app/core/services/configuration/export-setting.service.ts
+++ b/src/app/core/services/configuration/export-setting.service.ts
@@ -1,9 +1,14 @@
import { Injectable } from '@angular/core';
import { Observable } from 'rxjs';
-import { ExportSettingGet, ExportSettingPost } from '../../models/configuration/export-setting.model';
+import { ExportSettingGet, ExportSettingPost, ExportSettingFormOption, NameInJournalEntryOptions } from '../../models/configuration/export-setting.model';
import { ApiService } from '../core/api.service';
import { WorkspaceService } from '../workspace/workspace.service';
+import { AutoMapEmployee, CCCExpenseState, CorporateCreditCardExpensesObject, EmployeeFieldMapping, ExpenseGroupingFieldOption, ExpenseState, ExportDateType, NameInJournalEntry, ReimbursableExpensesObject } from '../../models/enum/enum.model';
+import { AbstractControl, FormGroup, ValidatorFn, Validators } from '@angular/forms';
+import { EmployeeSettingFormOption } from '../../models/configuration/employee-setting.model';
+
+
@Injectable({
providedIn: 'root'
})
@@ -21,4 +26,302 @@ export class ExportSettingService {
postExportSettings(exportSettingsPayload: ExportSettingPost): Observable {
return this.apiService.put(`/v2/workspaces/${this.workspaceService.getWorkspaceId()}/export_settings/`, exportSettingsPayload);
}
+
+ createReimbursableExpenseWatcher(form: FormGroup, exportSettings: ExportSettingGet): void {
+ form.controls.reimbursableExpense.valueChanges.subscribe((isReimbursableExpenseSelected) => {
+ if (isReimbursableExpenseSelected) {
+ form.controls.expenseState.setValidators(Validators.required);
+ form.controls.expenseState.setValue(exportSettings.expense_group_settings?.expense_state ? exportSettings.expense_group_settings?.expense_state : ExpenseState.PAYMENT_PROCESSING);
+ form.controls.reimbursableExportType.setValidators(Validators.required);
+ form.controls.reimbursableExportGroup.setValidators(Validators.required);
+ form.controls.reimbursableExportDate.setValidators(Validators.required);
+ } else {
+ form.controls.expenseState.clearValidators();
+ form.controls.reimbursableExportType.clearValidators();
+ form.controls.reimbursableExportGroup.clearValidators();
+ form.controls.reimbursableExportDate.clearValidators();
+ form.controls.expenseState.setValue(null);
+ form.controls.reimbursableExportType.setValue(null);
+ form.controls.reimbursableExportGroup.setValue(null);
+ form.controls.reimbursableExportDate.setValue(null);
+ }
+ });
+ }
+
+ createCreditCardExpenseWatcher(form: FormGroup, exportSettings: ExportSettingGet): void {
+ form.controls.creditCardExpense.valueChanges.subscribe((isCreditCardExpenseSelected) => {
+ if (isCreditCardExpenseSelected) {
+ form.controls.cccExpenseState.setValidators(Validators.required);
+ form.controls.cccExpenseState.setValue(exportSettings.expense_group_settings?.ccc_expense_state ? exportSettings.expense_group_settings?.ccc_expense_state : exportSettings.workspace_general_settings.is_simplify_report_closure_enabled ? CCCExpenseState.APPROVED: CCCExpenseState.PAYMENT_PROCESSING);
+ form.controls.creditCardExportType.setValidators(Validators.required);
+ form.controls.creditCardExportGroup.setValidators(Validators.required);
+ form.controls.creditCardExportDate.setValidators(Validators.required);
+ } else {
+ form.controls.cccExpenseState.clearValidators();
+ form.controls.creditCardExportType.clearValidators();
+ form.controls.creditCardExportGroup.clearValidators();
+ form.controls.creditCardExportDate.clearValidators();
+ form.controls.cccExpenseState.setValue(null);
+ form.controls.creditCardExportType.setValue(null);
+ form.controls.creditCardExportGroup.setValue(null);
+ form.controls.creditCardExportDate.setValue(null);
+ }
+ });
+ }
+
+ getExportGroup(exportGroups: string[] | null): string {
+ if (exportGroups) {
+ const exportGroup = exportGroups.find((exportGroup) => {
+ return exportGroup === ExpenseGroupingFieldOption.EXPENSE_ID || exportGroup === ExpenseGroupingFieldOption.CLAIM_NUMBER || exportGroup === ExpenseGroupingFieldOption.SETTLEMENT_ID;
+ });
+ return exportGroup ? exportGroup : ExpenseGroupingFieldOption.CLAIM_NUMBER;
+ }
+
+ return '';
+ }
+
+ nameInJournalOptions(): NameInJournalEntryOptions[] {
+ return [
+ {
+ label: 'Merchant Name',
+ value: NameInJournalEntry.MERCHANT
+ },
+ {
+ label: 'Employee Name',
+ value: NameInJournalEntry.EMPLOYEE
+ }
+ ];
+ }
+
+ getReimbursableExpenseGroupingFieldOptions() {
+ return [
+ {
+ label: 'Report',
+ value: ExpenseGroupingFieldOption.CLAIM_NUMBER
+ },
+ {
+ label: 'Payment',
+ value: ExpenseGroupingFieldOption.SETTLEMENT_ID
+ },
+ {
+ label: 'Expense',
+ value: ExpenseGroupingFieldOption.EXPENSE_ID
+ }
+ ];
+ }
+
+ getReimbursableExportTypeOptions(employeeFieldMapping: EmployeeFieldMapping): ExportSettingFormOption[] {
+ return {
+ EMPLOYEE: [
+ {
+ label: 'Check',
+ value: ReimbursableExpensesObject.CHECK
+ },
+ {
+ label: 'Expense',
+ value: ReimbursableExpensesObject.EXPENSE
+ },
+ {
+ label: 'Journal Entry',
+ value: ReimbursableExpensesObject.JOURNAL_ENTRY
+ }
+ ],
+ VENDOR: [
+ {
+ label: 'Bill',
+ value: ReimbursableExpensesObject.BILL
+ },
+ {
+ label: 'Expense',
+ value: ReimbursableExpensesObject.EXPENSE
+ },
+ {
+ label: 'Journal Entry',
+ value: ReimbursableExpensesObject.JOURNAL_ENTRY
+ }
+ ]
+ }[employeeFieldMapping];
+ }
+
+ getcreditCardExportTypes(): ExportSettingFormOption[] {
+ return [
+ {
+ label: 'Bill',
+ value: CorporateCreditCardExpensesObject.BILL
+ },
+ {
+ label: 'Credit Card Purchase',
+ value: CorporateCreditCardExpensesObject.CREDIT_CARD_PURCHASE
+ },
+ {
+ label: 'Journal Entry',
+ value: CorporateCreditCardExpensesObject.JOURNAL_ENTRY
+ },
+ {
+ label: 'Debit Card Expense',
+ value: CorporateCreditCardExpensesObject.DEBIT_CARD_EXPENSE
+ }
+ ];
+ }
+
+ getReimbursableExpenseGroupingDateOptions(): ExportSettingFormOption[] {
+ 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
+ }
+ ];
+ }
+
+ getReimbursableExpenseStateOptions(isSimplifyReportClosureEnabled: boolean): ExportSettingFormOption[] {
+ return [
+ {
+ label: isSimplifyReportClosureEnabled ? 'Processing' : 'Payment Processing',
+ value: ExpenseState.PAYMENT_PROCESSING
+ },
+ {
+ label: isSimplifyReportClosureEnabled ? 'Closed' : 'Paid',
+ value: ExpenseState.PAID
+ }
+ ];
+ }
+
+ getCCCExpenseStateOptions(isSimplifyReportClosureEnabled: boolean): ExportSettingFormOption[] {
+ return [
+ {
+ label: isSimplifyReportClosureEnabled ? 'Approved' : 'Payment Processing',
+ value: isSimplifyReportClosureEnabled ? CCCExpenseState.APPROVED: CCCExpenseState.PAYMENT_PROCESSING
+ },
+ {
+ label: isSimplifyReportClosureEnabled ? 'Closed' : 'Paid',
+ value: CCCExpenseState.PAID
+ }
+ ];
+ }
+
+ exportSelectionValidator(exportSettingsForm: FormGroup, isCloneSetting: boolean = false): ValidatorFn {
+ return (control: AbstractControl): {[key: string]: object} | null => {
+ let forbidden = true;
+ if (exportSettingsForm) {
+ if (typeof control.value === 'boolean') {
+ if (control.value) {
+ forbidden = false;
+ } else {
+ if (control.parent?.get('reimbursableExpense')?.value || control.parent?.get('creditCardExpense')?.value) {
+ forbidden = false;
+ }
+ }
+ } else if ((control.value === ExpenseState.PAID || control.value === ExpenseState.PAYMENT_PROCESSING || control.value === CCCExpenseState.APPROVED)
+ && (control.parent?.get('reimbursableExpense')?.value || control.parent?.get('creditCardExpense')?.value)) {
+ forbidden = false;
+ } else if (isCloneSetting && (control.parent?.get('reimbursableExpense')?.value || control.parent?.get('creditCardExpense')?.value)) {
+ forbidden = false;
+ }
+
+ if (!forbidden) {
+ control.parent?.get('expenseState')?.setErrors(null);
+ control.parent?.get('cccExpenseState')?.setErrors(null);
+ control.parent?.get('reimbursableExpense')?.setErrors(null);
+ control.parent?.get('creditCardExpense')?.setErrors(null);
+ return null;
+ }
+ }
+
+ return {
+ forbiddenOption: {
+ value: control.value
+ }
+ };
+ };
+ }
+
+ showExpenseAccountField(form: FormGroup): boolean {
+ return form.controls.reimbursableExportType.value === ReimbursableExpensesObject.EXPENSE;
+ }
+
+ showBankAccountField(form: FormGroup): boolean {
+ return form.value.employeeMapping === EmployeeFieldMapping.EMPLOYEE && form.controls.reimbursableExportType.value && form.controls.reimbursableExportType.value !== ReimbursableExpensesObject.EXPENSE;
+ }
+
+ showReimbursableAccountsPayableField(form: FormGroup): boolean {
+ return (form.controls.reimbursableExportType.value === ReimbursableExpensesObject.BILL) || (form.controls.reimbursableExportType.value === ReimbursableExpensesObject.JOURNAL_ENTRY && form.value.employeeMapping === EmployeeFieldMapping.VENDOR);
+ }
+
+ showCreditCardAccountField(form: FormGroup): boolean {
+ return form.controls.creditCardExportType.value && form.controls.creditCardExportType.value !== CorporateCreditCardExpensesObject.BILL && form.controls.creditCardExportType.value !== CorporateCreditCardExpensesObject.DEBIT_CARD_EXPENSE;
+ }
+
+ showDebitCardAccountField(form: FormGroup): boolean {
+ return form.controls.creditCardExportType.value && form.controls.creditCardExportType.value === CorporateCreditCardExpensesObject.DEBIT_CARD_EXPENSE;
+ }
+
+ showDefaultCreditCardVendorField(form: FormGroup): boolean {
+ return form.controls.creditCardExportType.value === CorporateCreditCardExpensesObject.BILL;
+ }
+
+ showCCCAccountsPayableField(form: FormGroup): boolean {
+ return form.controls.creditCardExportType.value === CorporateCreditCardExpensesObject.BILL;
+ }
+
+ setGeneralMappingsValidator(form: FormGroup): void {
+ if (this.showBankAccountField(form)) {
+ form.controls.bankAccount.setValidators(Validators.required);
+ } else {
+ form.controls.bankAccount.clearValidators();
+ form.controls.bankAccount.updateValueAndValidity();
+ }
+
+ if (this.showCreditCardAccountField(form)) {
+ form.controls.defaultCCCAccount.setValidators(Validators.required);
+ } else {
+ form.controls.defaultCCCAccount.clearValidators();
+ form.controls.defaultCCCAccount.updateValueAndValidity();
+ }
+
+ if (this.showDebitCardAccountField(form)) {
+ form.controls.defaultDebitCardAccount.setValidators(Validators.required);
+ } else {
+ form.controls.defaultDebitCardAccount.clearValidators();
+ form.controls.defaultDebitCardAccount.updateValueAndValidity();
+
+ }
+
+ if (this.showReimbursableAccountsPayableField(form) || this.showCCCAccountsPayableField(form)) {
+ form.controls.accountsPayable.setValidators(Validators.required);
+ } else {
+ form.controls.accountsPayable.clearValidators();
+ form.controls.accountsPayable.updateValueAndValidity();
+ }
+
+ if (this.showDefaultCreditCardVendorField(form)) {
+ form.controls.defaultCreditCardVendor.setValidators(Validators.required);
+ } else {
+ form.controls.defaultCreditCardVendor.clearValidators();
+ form.controls.defaultCreditCardVendor.updateValueAndValidity();
+ }
+
+ if (this.showExpenseAccountField(form)) {
+ form.controls.qboExpenseAccount.setValidators(Validators.required);
+ } else {
+ form.controls.qboExpenseAccount.clearValidators();
+ form.controls.qboExpenseAccount.updateValueAndValidity();
+ }
+ }
}
+
diff --git a/src/app/core/services/configuration/import-setting.service.spec.ts b/src/app/core/services/configuration/import-setting.service.spec.ts
index e3b04e6b..39501594 100644
--- a/src/app/core/services/configuration/import-setting.service.spec.ts
+++ b/src/app/core/services/configuration/import-setting.service.spec.ts
@@ -4,6 +4,11 @@ import { ImportSettingPost, ImportSettingModel } from '../../models/configuratio
import { MappingSourceField, MappingDestinationField } from '../../models/enum/enum.model';
import { HttpTestingController, HttpClientTestingModule } from '@angular/common/http/testing';
import { environment } from 'src/environments/environment';
+import { MatLegacyDialog as MatDialog, MatLegacyDialogModule as MatDialogModule } from '@angular/material/legacy-dialog';
+
+import { FormBuilder } from '@angular/forms';
+import { of } from 'rxjs';
+import { mockPatchExpenseFieldsFormArray } from 'src/app/shared/components/configuration/import-settings/import-settings.fixture';
describe('ImportSettingService', () => {
let service: ImportSettingService;
@@ -11,17 +16,82 @@ describe('ImportSettingService', () => {
let httpMock: HttpTestingController;
const API_BASE_URL = environment.api_url;
const workspace_id = environment.tests.workspaceId;
+ let formbuilder: FormBuilder;
+ let dialogSpy: jasmine.Spy;
+ const dialogRefSpyObj = jasmine.createSpyObj({ afterClosed: of({source_field: MappingDestinationField.TAX_CODE,
+ destination_field: MappingDestinationField.CLASS,
+ import_to_fyle: true,
+ name: MappingDestinationField.TAX_CODE,
+ disable_import_to_fyle: true,
+ source_placeholder: 'close'}), close: null });
+ dialogRefSpyObj.componentInstance = { body: '' };
beforeEach(() => {
TestBed.configureTestingModule({
- imports: [HttpClientTestingModule],
+ imports: [HttpClientTestingModule, MatDialogModule],
providers: [ImportSettingService]
});
+ dialogSpy = spyOn(TestBed.get(MatDialog), 'open').and.returnValue(dialogRefSpyObj);
injector = getTestBed();
+ formbuilder = TestBed.inject(FormBuilder);
service = injector.inject(ImportSettingService);
httpMock = injector.inject(HttpTestingController);
+
+ });
+
+ it('getQboExpenseFields function check', () => {
+ const qboAttributes = ['CUSTOMER'];
+ const mappingSettings = [
+ {
+ "source_field": "COST_CENTER",
+ "destination_field": "CUSTOMER",
+ "import_to_fyle": true,
+ "is_custom": false,
+ "source_placeholder": null
+ }
+ ];
+ const fyleFields = ['COST_CENTER', 'PROJECT'];
+ expect((service as any).getQboExpenseFields(qboAttributes, mappingSettings, true, fyleFields));
});
+ it('getExpenseFieldsFormArray function check', () => {
+ const mappingSettings = [
+ {
+ "source_field": "COST_CENTER",
+ "destination_field": "CUSTOMER",
+ "import_to_fyle": true,
+ "is_custom": false,
+ "source_placeholder": null
+ }
+ ];
+ expect((service as any).getExpenseFieldsFormArray(mappingSettings, false));
+ });
+
+ it('getPatchExpenseFieldValues function check', () => {
+ const mappingSettings =
+ {
+ "source_field": "COST_CENTER",
+ "destination_field": "CUSTOMER",
+ "import_to_fyle": true,
+ "disable_import_to_fyle": true,
+ "source_placeholder": '',
+ "addSourceField": false
+ };
+ expect((service as any).getPatchExpenseFieldValues("CUSTOMER", "COST_CENTER")).toEqual(mappingSettings);
+ });
+
+ it('importToggleWatcher function check', () => {
+ const form = formbuilder.group({
+ source_field: [MappingSourceField.PROJECT],
+ destination_field: [MappingDestinationField.CUSTOMER],
+ disable_import_to_fyle: [false],
+ import_to_fyle: [false, (service as any).importToggleWatcher()],
+ source_placeholder: ['']
+ });
+
+ form.controls.import_to_fyle.patchValue(true);
+ expect((service as any).importToggleWatcher());
+ });
it('should be created', () => {
expect(service).toBeTruthy();
@@ -91,4 +161,18 @@ describe('ImportSettingService', () => {
});
req.flush(response);
});
+
+ it('createExpenceField function check', () => {
+ const mappingSettings = [
+ {
+ "source_field": "COST_CENTER",
+ "destination_field": "CUSTOMER",
+ "import_to_fyle": true,
+ "is_custom": false,
+ "source_placeholder": null
+ }
+ ];
+ expect((service as any).createExpenseField(MappingDestinationField.CLASS, mappingSettings)).toBeUndefined();
+ expect(dialogSpy).toHaveBeenCalled();
+ });
});
diff --git a/src/app/core/services/configuration/import-setting.service.ts b/src/app/core/services/configuration/import-setting.service.ts
index 63397c16..c4cb7766 100644
--- a/src/app/core/services/configuration/import-setting.service.ts
+++ b/src/app/core/services/configuration/import-setting.service.ts
@@ -1,23 +1,115 @@
-import { Injectable } from '@angular/core';
-import { ImportSettingPost } from '../../models/configuration/import-setting.model';
+import { EventEmitter, Injectable, Output } from '@angular/core';
+import { ExpenseFieldsFormOption, ImportSettingPost } from '../../models/configuration/import-setting.model';
import { ApiService } from '../core/api.service';
import { WorkspaceService } from '../workspace/workspace.service';
+import { ExpenseField, ExpenseFieldFormArray } from '../../models/misc/expense-field.model';
+import { MappingSetting } from '../../models/db/mapping-setting.model';
+import { ExpenseFieldCreationDialogComponent } from 'src/app/shared/components/configuration/import-settings/expense-field-creation-dialog/expense-field-creation-dialog.component';
+import { MatLegacyDialog as MatDialog } from '@angular/material/legacy-dialog';
+import { AbstractControl, FormBuilder, FormGroup, ValidatorFn, Validators } from '@angular/forms';
+import { RxwebValidators } from '@rxweb/reactive-form-validators';
@Injectable({
providedIn: 'root'
})
export class ImportSettingService {
+ workspaceId = this.workspaceService.getWorkspaceId()
+
+ @Output() patchExpenseFieldEmitter: EventEmitter = new EventEmitter();
constructor(
private apiService: ApiService,
- private workspaceService: WorkspaceService
+ private workspaceService: WorkspaceService,
+ private formBuilder: FormBuilder,
+ private dialog: MatDialog
) { }
getImportSettings() {
- return this.apiService.get(`/v2/workspaces/${this.workspaceService.getWorkspaceId()}/import_settings/`, {});
+ return this.apiService.get(`/v2/workspaces/${this.workspaceId}/import_settings/`, {});
}
postImportSettings(exportSettingsPayload: ImportSettingPost){
- return this.apiService.put(`/v2/workspaces/${this.workspaceService.getWorkspaceId()}/import_settings/`, exportSettingsPayload);
+ return this.apiService.put(`/v2/workspaces/${this.workspaceId}/import_settings/`, exportSettingsPayload);
+ }
+
+ getQboExpenseFields(qboAttributes: ExpenseField[], mappingSettings: MappingSetting[], isCloneSettings: boolean = false, fyleFields: string[] = []): ExpenseFieldsFormOption[] {
+ return qboAttributes.map(attribute => {
+ const mappingSetting = mappingSettings.filter((mappingSetting: MappingSetting) => {
+ if (mappingSetting.destination_field.toUpperCase() === attribute.attribute_type) {
+ if (isCloneSettings) {
+ return fyleFields.includes(mappingSetting.source_field.toUpperCase()) ? mappingSetting : false;
+ }
+
+ return mappingSetting;
+ }
+ return false;
+ });
+ return {
+ source_field: mappingSetting.length > 0 ? mappingSetting[0].source_field : '',
+ destination_field: attribute.display_name,
+ import_to_fyle: mappingSetting.length > 0 ? mappingSetting[0].import_to_fyle : false,
+ disable_import_to_fyle: false,
+ source_placeholder: ''
+ };
+ });
+ }
+
+ private importToggleWatcher(): ValidatorFn {
+ return (control: AbstractControl): {[key: string]: object} | null => {
+ if (control.value) {
+ // Mark Fyle field as mandatory if toggle is enabled
+ control.parent?.get('source_field')?.setValidators(Validators.required);
+ control.parent?.get('source_field')?.setValidators(RxwebValidators.unique());
+ } else {
+ // Reset Fyle field if toggle is disabled
+ control.parent?.get('source_field')?.clearValidators();
+ control.parent?.get('source_field')?.setValue(null);
+ }
+
+ return null;
+ };
+ }
+
+ getExpenseFieldsFormArray(qboExpenseField: ExpenseFieldsFormOption[], isWatcherRequired: boolean): FormGroup[] {
+ return qboExpenseField.map((field) => {
+ return this.formBuilder.group({
+ source_field: [field.source_field, Validators.required],
+ destination_field: [field.destination_field.toUpperCase()],
+ disable_import_to_fyle: [field.disable_import_to_fyle],
+ import_to_fyle: [field.import_to_fyle, isWatcherRequired ? this.importToggleWatcher() : ''],
+ source_placeholder: ['']
+ });
+ });
+ }
+
+ private getPatchExpenseFieldValues(destinationType: string, sourceField: string = '', source_placeholder: string = '', addSourceField: boolean = false): ExpenseFieldFormArray {
+ return {
+ source_field: sourceField,
+ destination_field: destinationType,
+ import_to_fyle: sourceField ? true : false,
+ disable_import_to_fyle: sourceField ? true : false,
+ source_placeholder: source_placeholder,
+ addSourceField: addSourceField
+ };
+ }
+
+ createExpenseField(destinationType: string, mappingSettings: MappingSetting[]): void {
+ const existingFields = mappingSettings.map(setting => setting.source_field.split('_').join(' '));
+ const dialogRef = this.dialog.open(ExpenseFieldCreationDialogComponent, {
+ width: '551px',
+ data: existingFields
+ });
+
+ const expenseFieldValue = this.getPatchExpenseFieldValues(destinationType);
+ this.patchExpenseFieldEmitter.emit(expenseFieldValue);
+
+ dialogRef.afterClosed().subscribe((expenseField) => {
+ if (expenseField) {
+ const sourceType = expenseField.name.split(' ').join('_').toUpperCase();
+
+ const expenseFieldValue = this.getPatchExpenseFieldValues(destinationType, sourceType, expenseField.source_placeholder, true);
+ this.patchExpenseFieldEmitter.emit(expenseFieldValue);
+ }
+ });
}
}
diff --git a/src/app/core/services/core/helper.service.spec.ts b/src/app/core/services/core/helper.service.spec.ts
index 29e84253..4b3d32fa 100644
--- a/src/app/core/services/core/helper.service.spec.ts
+++ b/src/app/core/services/core/helper.service.spec.ts
@@ -1,14 +1,23 @@
import { TestBed } from '@angular/core/testing';
-import { UntypedFormControl, UntypedFormGroup } from '@angular/forms';
+import { FormControl, FormGroup } from '@angular/forms';
import { DefaultDestinationAttribute } from '../../models/db/general-mapping.model';
import { HelperService } from './helper.service';
+import { Router } from '@angular/router';
+import { MatLegacyDialogModule as MatDialogModule } from '@angular/material/legacy-dialog';
describe('HelperService', () => {
let service: HelperService;
+ const routerSpy = { navigate: jasmine.createSpy('navigate'), url: '/path' };
+ let router: Router;
beforeEach(() => {
- TestBed.configureTestingModule({});
+ TestBed.configureTestingModule({
+ providers: [
+ { provide: Router, useValue: routerSpy }
+ ],
+ imports: [MatDialogModule]
+ });
service = TestBed.inject(HelperService);
});
@@ -17,8 +26,8 @@ describe('HelperService', () => {
});
it('clearSearchText service check', () => {
- const form=new UntypedFormGroup({
- searchOption: new UntypedFormControl('fyle')
+ const form=new FormGroup({
+ searchOption: new FormControl('fyle')
});
service.clearSearchText(form);
expect(form.controls.searchOption.value).toBeNull();
diff --git a/src/app/core/services/core/helper.service.ts b/src/app/core/services/core/helper.service.ts
index b976cf03..df36682d 100644
--- a/src/app/core/services/core/helper.service.ts
+++ b/src/app/core/services/core/helper.service.ts
@@ -4,6 +4,10 @@ import { UntypedFormGroup } from '@angular/forms';
import { SnakeCaseToSpaceCase } from 'src/app/shared/pipes/snake-case-to-space-case.pipe';
import { DefaultDestinationAttribute } from '../../models/db/general-mapping.model';
import { WindowService } from './window.service';
+import { ConfirmationDialog } from '../../models/misc/confirmation-dialog.model';
+import { ConfirmationDialogComponent } from 'src/app/shared/components/core/confirmation-dialog/confirmation-dialog.component';
+import { Router } from '@angular/router';
+import { MatLegacyDialog as MatDialog} from '@angular/material/legacy-dialog';
@Injectable({
providedIn: 'root'
@@ -12,7 +16,11 @@ export class HelperService {
private windowReference: Window;
- constructor(private windowService: WindowService) {
+ constructor(
+ private windowService: WindowService,
+ private dialog: MatDialog,
+ private router: Router
+ ) {
this.windowReference = this.windowService.nativeWindow;
}
@@ -37,4 +45,17 @@ export class HelperService {
getSpaceCasedTitleCase(word: string): string {
return new SnakeCaseToSpaceCase().transform((new TitleCasePipe().transform(word)));
}
+
+ openDialogAndSetupRedirection(data: ConfirmationDialog, url: string): void {
+ const dialogRef = this.dialog.open(ConfirmationDialogComponent, {
+ width: '551px',
+ data: data
+ });
+
+ dialogRef.afterClosed().subscribe((ctaClicked: boolean) => {
+ if (ctaClicked) {
+ this.router.navigate([url]);
+ }
+ });
+ }
}
diff --git a/src/app/core/services/integration/tracking.service.ts b/src/app/core/services/integration/tracking.service.ts
index a118a138..04c683de 100644
--- a/src/app/core/services/integration/tracking.service.ts
+++ b/src/app/core/services/integration/tracking.service.ts
@@ -136,4 +136,8 @@ export class TrackingService {
onMappingsAlphabeticalFilter(properties: MappingAlphabeticalFilterAdditionalProperty): void {
this.eventTrack('Mappings Alphabetical Filter', properties);
}
+
+ onCloneSettingsSave(properties: Partial): void {
+ this.eventTrack('Clone Settings Saved', properties);
+ }
}
diff --git a/src/app/core/services/misc/mapping.service.ts b/src/app/core/services/misc/mapping.service.ts
index b4f28fa6..04cc3c12 100644
--- a/src/app/core/services/misc/mapping.service.ts
+++ b/src/app/core/services/misc/mapping.service.ts
@@ -215,5 +215,4 @@ export class MappingService {
}
return undefined;
}
-
}
diff --git a/src/app/integration/integration.module.ts b/src/app/integration/integration.module.ts
index d760d555..9127d339 100644
--- a/src/app/integration/integration.module.ts
+++ b/src/app/integration/integration.module.ts
@@ -5,7 +5,6 @@ import { IntegrationRoutingModule } from './integration-routing.module';
import { SharedModule } from '../shared/shared.module';
-
@NgModule({
declarations: [
IntegrationComponent
diff --git a/src/app/integration/main/mapping/employee-mapping/employee-mapping.component.spec.ts b/src/app/integration/main/mapping/employee-mapping/employee-mapping.component.spec.ts
index 8c40d6d4..8d6105ba 100644
--- a/src/app/integration/main/mapping/employee-mapping/employee-mapping.component.spec.ts
+++ b/src/app/integration/main/mapping/employee-mapping/employee-mapping.component.spec.ts
@@ -15,6 +15,7 @@ import { mappingList } from 'src/app/shared/components/mapping/mapping-table/map
import { environment } from 'src/environments/environment';
import { EmployeeMappingComponent } from './employee-mapping.component';
import { employeeMappingResponse, getEmployeeMappingResponse, getEmployeeMappingResponse1, mappinglist, MappingStatsResponse, qboData, qboData2, workspaceGeneralSettingResponse } from './employee-mapping.fixture';
+import { MatLegacyDialogModule as MatDialogModule } from '@angular/material/legacy-dialog';
describe('EmployeeMappingComponent', () => {
let component: EmployeeMappingComponent;
@@ -44,7 +45,7 @@ describe('EmployeeMappingComponent', () => {
getPageSize: () => 10
};
await TestBed.configureTestingModule({
- imports: [ FormsModule, ReactiveFormsModule, HttpClientModule, MatSnackBarModule, HttpClientTestingModule, NoopAnimationsModule ],
+ imports: [ FormsModule, ReactiveFormsModule, HttpClientModule, MatSnackBarModule, MatDialogModule, HttpClientTestingModule, NoopAnimationsModule ],
declarations: [ EmployeeMappingComponent ],
providers: [
{ provide: WorkspaceService, useValue: service1 },
diff --git a/src/app/integration/onboarding/clone-settings/clone-settings.component.html b/src/app/integration/onboarding/clone-settings/clone-settings.component.html
new file mode 100644
index 00000000..3b2ee8c0
--- /dev/null
+++ b/src/app/integration/onboarding/clone-settings/clone-settings.component.html
@@ -0,0 +1,962 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Employee Representation
+
+
+
+
+
+
+
+
+
+
+
+
Employee Mapping
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Reimbursable Expense
+
+
+
+
+
+
+
+
+
+
+
+
+
State of Export
+
+
+
+
+
+
+
+
+
+
+
+
Mode of Export
+
+
+
+
+
+
+
+
+
+
+
{{ "To which Bank Account should the " +
+ getExportType(cloneSettingsForm.value.reimbursableExportType) + " be posted to?"}}
+
+
+
+
+
+
+
+
+
+
{{"To which Expense Account should the " +
+ getExportType(cloneSettingsForm.value.reimbursableExportType) + " be posted to?"}}
+
+
+
+
+
+
+
+
+
+
To which Accounts Payable account should the
+ Bill be posted to?
+
+
+
+
+
+
+
+
+
+
+
+
{{"Set the " +
+ getExportType(cloneSettingsForm.value.reimbursableExportType) + " date as"}}
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{generateGroupingLabel(exportSource.REIMBURSABLE)}}
+
+
+
+
+
+
+
+
+
+
+
Corporate Card Expense
+
+
+
+
+
+
+
+
+
+
+
+
State of Export
+
+
+
+
+
+
+
+
+
+
+
+
Mode of Export
+
+
+
+
+
+
+
+
+
+
+
Set Default Credit Card Account as
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Select Accounts Payable Account
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Name in Journal Entry (CCC)
+
+
+
+
+
+
+
+
+
+
+
+
+ {{generateGroupingLabel(exportSource.CREDIT_CARD)}}
+
+
+
+
+
+
+
+
+
+
+
+
+
{{"Set the " +
+ getExportType(cloneSettingsForm.value.creditCardExportType) + " date as"}}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Chart of Accounts
+
+
+
+
+
+
+
+ View Accounts
+
+
+
+
+
+ {{ chartOfAccountType.value.name }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Products / Service
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{ expenseField.value.destination_field | titlecase }}
+
+
+
+
+
+
+
+
+ {{ fyleExpenseField.split('_').join(' ') | titlecase }}
+
+
+
+
+
+
+
+
or
+
+ Create a new field in Fyle
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{ option.value }}
+ {{ option.value }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Add Fields
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/app/integration/onboarding/clone-settings/clone-settings.component.scss b/src/app/integration/onboarding/clone-settings/clone-settings.component.scss
new file mode 100644
index 00000000..44ef2bf2
--- /dev/null
+++ b/src/app/integration/onboarding/clone-settings/clone-settings.component.scss
@@ -0,0 +1,222 @@
+.clone-settings {
+ &--dropdown-section {
+ padding-top: 30px;
+ }
+
+ &--section {
+ display: flex;
+ justify-content: center;
+ }
+
+ &--block {
+ margin-top: -140px;
+ margin-bottom: 58px;
+ width: 1098px;
+ background: #FFFFFF;
+ border: 1px solid #F5F5F5;
+ box-sizing: border-box;
+ box-shadow: 0px 4px 8px -2px rgba(16, 24, 40, 0.1), 0px 2px 4px -2px rgba(16, 24, 40, 0.06);
+ border-radius: 8px;
+ }
+
+ &--export-dropdown {
+ margin-top: 50px;
+ }
+
+ &--header-section {
+ padding-top: 24px;
+ background: #FAFCFF;
+ border: 1px solid #F5F5F5;
+ }
+
+ &--header-icon {
+ padding: 0px 10px 0px 32px;
+ }
+
+ &--header-caption {
+ padding-top: 10px;
+ padding-bottom: 24px;
+ }
+
+ &--configuration-section {
+ padding: 24px 32px;
+ }
+
+ &--export-section {
+ padding: 0px 32px;
+ margin: -10px 0px -10px 0px;
+ }
+
+ &--info-icon {
+ padding: 4px 0px 0px 10px;
+ }
+
+ &--sub-options-icon {
+ padding-right: 12px;
+ }
+
+ &--field-section {
+ width: 500px;
+ }
+
+ &--sub-options-text {
+ color: #2C304E;
+ font-size: 14px;
+ }
+
+ &--export-type {
+ padding-left: 10px;
+ }
+
+ &--export-type-text {
+ font-weight: 600;
+ }
+
+ &--field-header-section {
+ padding: 8px 0px 0px 32px;
+ }
+
+ &--field-header {
+ height: 48px;
+ font-size: 14px;
+ }
+
+ &--qbo-header {
+ padding-left: 13px;
+ padding-right: 202px;
+ }
+
+ &--coa-import-section {
+ padding: 20px 0px 12px 32px;
+ }
+
+ &--qbo-field {
+ height: 34px;
+ width: 304px;
+ background: #F5F5F5;
+ border: 1px solid #DFDFE2;
+ border-radius: 4px;
+ font-size: 14px;
+ color: #2C304E;
+ }
+
+ &--qbo-field-text {
+ padding-left: 14px;
+ }
+
+ &--back-btn {
+ margin-right: 12px;
+ }
+
+ &--error-message {
+ padding-top: 10px;
+ margin-left: -22px;
+ }
+
+ &--reset-btn {
+ width: 186px;
+ }
+
+ &--footer-inner-section {
+ height: 84px;
+ padding: 12px 32px;
+ }
+
+ &--coa-list {
+ padding-left: 12px;
+ font-size: 14px;
+ }
+
+ &--cta-text {
+ color: #FF3366;
+ }
+
+ &--coa-list-icon {
+ padding-left: 15px;
+ }
+
+ &--delete-icon {
+ padding-right: 34px;
+ }
+
+ &--tax-group-field {
+ margin-right: 12px;
+ }
+
+ &--header-advanced-setting {
+ padding-top: 24px;
+ }
+
+ &--configuration-payment-section {
+ padding: 0px 32px 24px;
+ }
+
+ &--configuration-payment-field {
+ padding-left: 28px;
+ }
+
+ &--add-email-text {
+ padding-right: 210px;
+ padding-top: 18px;
+ }
+
+ &--add-btn {
+ margin-bottom: 8px;
+ height: 16px;
+ width: 16px;
+ }
+
+ &--span-or {
+ margin: 1px 15px 8px 5px ;
+ font-weight: 500;
+ font-size: 14px;
+ color: #2c304e;
+ }
+
+ &--additional-email-text {
+ margin-top: -40px;
+ }
+
+ &--add-field-section {
+ width: 124px;
+ }
+
+ &--add-field {
+ padding-right: 12px;
+ font-size: 14px;
+ }
+
+ &--memo-preview-section {
+ padding-top: 24px;
+ }
+
+ &--memo-preview-text {
+ font-weight: 500;
+ }
+
+ &--memo-preview-area {
+ padding: 12px 12px 0px 0px;
+ }
+
+ &--memo-preview {
+ padding: 8px 0px 8px 8px;
+ background: #F5F5F5;
+ border-radius: 4px;
+ display: flex;
+ align-items: center;
+ }
+
+ &--memo-preview-select {
+ ::ng-deep .mat-select-panel-wrap {
+ // TODO: check this
+ margin-left: 7% !important;
+ flex-basis: 96% !important;
+ }
+
+ ::ng-deep .cdk-overlay-pane {
+ // TODO: check this
+ margin-left: 1.5% !important;
+ }
+ }
+}
+
diff --git a/src/app/integration/onboarding/clone-settings/clone-settings.component.spec.ts b/src/app/integration/onboarding/clone-settings/clone-settings.component.spec.ts
new file mode 100644
index 00000000..9072a7c3
--- /dev/null
+++ b/src/app/integration/onboarding/clone-settings/clone-settings.component.spec.ts
@@ -0,0 +1,428 @@
+import { ComponentFixture, TestBed } from '@angular/core/testing';
+import { NoopAnimationsModule } from '@angular/platform-browser/animations';
+import { CloneSettingsComponent } from './clone-settings.component';
+import { HttpClientModule } from '@angular/common/http';
+import { FormBuilder, UntypedFormBuilder, Validators } from '@angular/forms';
+import { Router } from '@angular/router';
+import { of } from 'rxjs';
+import { mockCloneSettingExist, mockCloneSettingsGet, mockGroupedDestinationAttribtues } from './clone-settings.fixture';
+import { CloneSettingService } from 'src/app/core/services/configuration/clone-setting.service';
+import { MappingService } from 'src/app/core/services/misc/mapping.service';
+import { chartOfAccountTypesList, expenseFieldresponse, mockExpenseFieldsFormArray, mockPatchExpenseFieldsFormArray, qboField } from 'src/app/shared/components/configuration/import-settings/import-settings.fixture';
+import { getMappingSettingResponse } from 'src/app/shared/components/mapping/generic-mapping/generic-mapping.fixture';
+import { AdvancedSettingService } from 'src/app/core/services/configuration/advanced-setting.service';
+import { MatMenuModule } from '@angular/material/menu';
+import { MatLegacyDialogModule as MatDialogModule, MatLegacyDialog } from '@angular/material/legacy-dialog';
+import { MatLegacySnackBarModule as MatSnackBarModule } from '@angular/material/legacy-snack-bar';
+import { CorporateCreditCardExpensesObject, EmployeeFieldMapping, ExpenseGroupingFieldOption, ExportDateType, ExportSource, MappingDestinationField, MappingSourceField, ReimbursableExpensesObject } from 'src/app/core/models/enum/enum.model';
+import { ExportSettingService } from 'src/app/core/services/configuration/export-setting.service';
+import { ImportSettingService } from 'src/app/core/services/configuration/import-setting.service';
+import { mockNameInJournalEntry, mockReimbursableExpenseGroupingDateOptions, mockReimbursableExpenseGroupingFieldOptions, mockReimbursableExpenseStateOptions } from 'src/app/shared/components/configuration/export-settings/export-settings.fixture';
+import { adminEmails, destinationAttribute, memo, paymentSyncOptions, previewResponse } from 'src/app/shared/components/configuration/advanced-settings/advanced-settings.fixture';
+
+
+describe('CloneSettingsComponent', () => {
+ let component: CloneSettingsComponent;
+ let fixture: ComponentFixture;
+ let router: Router;
+ let formbuilder: UntypedFormBuilder;
+ let exportSettingService: ExportSettingService;
+ let importSettingService: ImportSettingService;
+ let cloneSettingService: CloneSettingService;
+ const routerSpy = { navigate: jasmine.createSpy('navigate'), url: '/path' };
+ let dialogSpy: jasmine.Spy;
+ const dialogRefSpyObj = jasmine.createSpyObj({ afterClosed: of({}), close: '' });
+ dialogRefSpyObj.componentInstance = { body: '' };
+ let service1: any;
+ let service2: any;
+ let service3: any;
+ let service4: any;
+
+ beforeEach(async () => {
+ service1 = {
+ checkCloneSettingsExists: () => of(mockCloneSettingExist),
+ postCloneSettings: () => of(mockCloneSettingsGet),
+ getCloneSettings: () => of(mockCloneSettingsGet)
+ };
+ service2 = {
+ getGroupedQBODestinationAttributes: () => of(mockGroupedDestinationAttribtues),
+ getFyleExpenseFields: () => of(expenseFieldresponse),
+ getMappingSettings: () => of(getMappingSettingResponse),
+ getQBODestinationAttributes: () => null,
+ getExpenseFieldsFormArray: () => mockExpenseFieldsFormArray
+ };
+ service3 = {
+ getPaymentSyncOptions: () => of(paymentSyncOptions),
+ getFrequencyIntervals: () => null,
+ getWorkspaceAdmins: () => null,
+ openAddemailDialog: () => null,
+ patchAdminEmailsEmitter: of(mockPatchExpenseFieldsFormArray)
+ };
+ service4 = {
+ exportSelectionValidator: () => undefined,
+ createCreditCardExpenseWatcher: () => undefined,
+ createReimbursableExpenseWatcher: () => undefined,
+ getReimbursableExpenseGroupingFieldOptions: () => mockReimbursableExpenseGroupingFieldOptions,
+ getReimbursableExpenseGroupingDateOptions: () => mockReimbursableExpenseGroupingDateOptions,
+ getcreditCardExportTypes: () => undefined,
+ getReimbursableExportTypeOptions: () => undefined,
+ getCCCExpenseStateOptions: () => undefined,
+ getExportGroup: () => undefined,
+ getReimbursableExpenseStateOptions: () => mockReimbursableExpenseStateOptions,
+ setGeneralMappingsValidator: () => undefined,
+ nameInJournalOptions: () => mockNameInJournalEntry
+ };
+ await TestBed.configureTestingModule({
+ declarations: [ CloneSettingsComponent ],
+ imports: [
+ HttpClientModule, MatDialogModule, MatSnackBarModule, MatMenuModule, NoopAnimationsModule
+ ],
+ providers: [
+ FormBuilder,
+ { provide: Router, useValue: routerSpy },
+ { provide: ExportSettingService, useValue: service4},
+ { provide: CloneSettingService, useValue: service1 },
+ { provide: MappingService, useValue: service2 },
+ { provide: AdvancedSettingService, useValue: service3 },
+ { provide: ExportSettingService, useValue: service4}
+ ]
+ })
+ .compileComponents();
+ });
+
+ beforeEach(() => {
+ fixture = TestBed.createComponent(CloneSettingsComponent);
+ component = fixture.componentInstance;
+ router = TestBed.inject(Router);
+ formbuilder = TestBed.inject(UntypedFormBuilder);
+ dialogSpy = spyOn(TestBed.get(MatLegacyDialog), 'open').and.returnValue(dialogRefSpyObj);
+ exportSettingService = TestBed.inject(ExportSettingService);
+ importSettingService = TestBed.inject(ImportSettingService);
+ component.cloneSettings = mockCloneSettingsGet;
+ component.qboExpenseFields = qboField;
+ component.chartOfAccountTypesList = chartOfAccountTypesList;
+
+
+ component.cloneSettingsForm = formbuilder.group({
+ employeeMapping: [component.cloneSettings.employee_mappings.workspace_general_settings?.employee_field_mapping, Validators.required],
+ autoMapEmployee: [component.cloneSettings.employee_mappings.workspace_general_settings?.auto_map_employees, Validators.nullValidator],
+
+ // Export Settings
+ reimbursableExpense: [component.cloneSettings.export_settings.workspace_general_settings?.reimbursable_expenses_object ? true : false],
+ reimbursableExportDate: [component.cloneSettings.export_settings.expense_group_settings?.reimbursable_export_date_type],
+ expenseState: [component.cloneSettings.export_settings.expense_group_settings?.expense_state],
+ reimbursableExportGroup: [exportSettingService.getExportGroup(component.cloneSettings.export_settings.expense_group_settings?.reimbursable_expense_group_fields)],
+ reimbursableExportType: [component.cloneSettings.export_settings.workspace_general_settings?.reimbursable_expenses_object],
+
+ creditCardExpense: [component.cloneSettings.export_settings.workspace_general_settings?.corporate_credit_card_expenses_object ? true : false],
+ creditCardExportDate: [component.cloneSettings.export_settings.expense_group_settings?.ccc_export_date_type],
+ cccExpenseState: [component.cloneSettings.export_settings.expense_group_settings?.ccc_expense_state],
+ creditCardExportGroup: [exportSettingService.getExportGroup(component.cloneSettings.export_settings.expense_group_settings?.corporate_credit_card_expense_group_fields)],
+ creditCardExportType: [component.cloneSettings.export_settings.workspace_general_settings?.corporate_credit_card_expenses_object],
+
+ bankAccount: [component.cloneSettings.export_settings.general_mappings?.bank_account?.id ? component.cloneSettings.export_settings.general_mappings.bank_account : null],
+ qboExpenseAccount: [component.cloneSettings.export_settings.general_mappings?.qbo_expense_account?.id ? component.cloneSettings.export_settings.general_mappings.qbo_expense_account : null],
+ defaultCCCAccount: [component.cloneSettings.export_settings.general_mappings?.default_ccc_account?.id ? component.cloneSettings.export_settings.general_mappings.default_ccc_account : null],
+ accountsPayable: [component.cloneSettings.export_settings.general_mappings?.accounts_payable?.id ? component.cloneSettings.export_settings.general_mappings.accounts_payable : null],
+ defaultCreditCardVendor: [component.cloneSettings.export_settings.general_mappings?.default_ccc_vendor?.id ? component.cloneSettings.export_settings.general_mappings.default_ccc_vendor : null],
+ defaultDebitCardAccount: [component.cloneSettings.export_settings.general_mappings?.default_debit_card_account?.id ? component.cloneSettings.export_settings.general_mappings.default_debit_card_account : null],
+ searchOption: [],
+
+ // Import Settings
+ chartOfAccount: [component.cloneSettings.import_settings.workspace_general_settings.import_categories],
+ importItems: [component.cloneSettings.import_settings.workspace_general_settings.import_items],
+ chartOfAccountTypes: formbuilder.array([]),
+ expenseFields: formbuilder.array([]),
+ taxCode: [component.cloneSettings.import_settings.workspace_general_settings.import_tax_codes],
+ defaultTaxCode: [component.cloneSettings.import_settings.general_mappings?.default_tax_code?.id ? component.cloneSettings.import_settings.general_mappings.default_tax_code : null],
+ importVendorsAsMerchants: [component.cloneSettings.import_settings.workspace_general_settings.import_vendors_as_merchants],
+ memoStructure: [component.cloneSettings.advanced_configurations.workspace_general_settings.memo_structure]
+ });
+
+ cloneSettingService = TestBed.inject(CloneSettingService);
+ fixture.detectChanges();
+ });
+
+ it('should create', () => {
+ expect(component).toBeTruthy();
+ });
+
+ it('getExportType function check', () => {
+ const response = ReimbursableExpensesObject.JOURNAL_ENTRY;
+ const output = response.toLowerCase().charAt(0).toUpperCase() + response.toLowerCase().slice(1);
+ expect(component.getExportType(ReimbursableExpensesObject.JOURNAL_ENTRY)).toEqual(output);
+ });
+
+ it('createCreditCardExportGroupWatcher function check', () => {
+ component.cloneSettingsForm.controls.creditCardExportGroup.patchValue(!component.cloneSettingsForm.controls.creditCardExportGroup.value);
+ expect((component as any).createCreditCardExportGroupWatcher()).toBeUndefined();
+ component.cloneSettingsForm.controls.creditCardExpense.patchValue(!component.cloneSettingsForm.controls.creditCardExportGroup.value);
+ component.cccExpenseGroupingDateOptions = [{
+ 'label': 'Posted Date',
+ 'value': ExportDateType.POSTED_AT
+ },
+ {
+ 'label': 'Spend Date',
+ 'value': ExportDateType.SPENT_AT
+ }];
+
+ component.cloneSettingsForm.controls.creditCardExportGroup.patchValue(ExpenseGroupingFieldOption.EXPENSE_ID);
+ expect((component as any).createCreditCardExportGroupWatcher()).toBeUndefined();
+ component.cloneSettingsForm.controls.creditCardExportGroup.patchValue(ExpenseGroupingFieldOption.CLAIM_NUMBER);
+ expect((component as any).createCreditCardExportGroupWatcher()).toBeUndefined();
+ });
+
+ it('createReimbursableExportGroupWatcher function check', () => {
+ component.cloneSettingsForm.controls.reimbursableExportGroup.patchValue(ExpenseGroupingFieldOption.EXPENSE_ID);
+ expect((component as any).createReimbursableExportGroupWatcher()).toBeUndefined();
+ component.cloneSettingsForm.controls.reimbursableExportGroup.patchValue(ExpenseGroupingFieldOption.SETTLEMENT_ID);
+ expect((component as any).createReimbursableExportGroupWatcher()).toBeUndefined();
+ });
+
+
+ it('showBankAccountField function check', () => {
+ component.employeeFieldMapping = EmployeeFieldMapping.EMPLOYEE;
+ component.cloneSettingsForm.controls.reimbursableExportType.patchValue(ReimbursableExpensesObject.BILL);
+ fixture.detectChanges();
+ expect(component.showBankAccountField()).toBeTrue();
+ });
+
+ it('showReimbursableAccountsPayableField function check', () => {
+ component.cloneSettingsForm.controls.employeeMapping.patchValue(EmployeeFieldMapping.VENDOR);
+ component.cloneSettingsForm.controls.reimbursableExportType.patchValue(ReimbursableExpensesObject.JOURNAL_ENTRY);
+ fixture.detectChanges();
+ expect(component.showReimbursableAccountsPayableField()).toBeTrue();
+ });
+
+ it('showCCCAccountsPayableField function check', () => {
+ component.cloneSettingsForm.controls.creditCardExportType.patchValue(CorporateCreditCardExpensesObject.BILL);
+ fixture.detectChanges();
+ expect(component.showCCCAccountsPayableField()).toBeTrue();
+ });
+
+ it('showDefaultCreditCardVendorField function check', () => {
+ component.cloneSettingsForm.controls.creditCardExportType.patchValue(CorporateCreditCardExpensesObject.BILL);
+ fixture.detectChanges();
+ expect(component.showDefaultCreditCardVendorField()).toBeTrue();
+ });
+
+ it('showExpenseAccountField function check', () => {
+ component.cloneSettingsForm.controls.reimbursableExportType.patchValue(ReimbursableExpensesObject.EXPENSE);
+ fixture.detectChanges();
+ expect(component.showExpenseAccountField()).toBeTrue();
+ });
+
+ it('showCreditCardAccountField function check', () => {
+ component.cloneSettingsForm.controls.creditCardExportType.patchValue(CorporateCreditCardExpensesObject.JOURNAL_ENTRY);
+ fixture.detectChanges();
+ expect(component.showCreditCardAccountField()).toBeTrue();
+ });
+
+ it('showDebitCardAccountField function check', () => {
+ component.cloneSettingsForm.controls.creditCardExportType.patchValue(CorporateCreditCardExpensesObject.DEBIT_CARD_EXPENSE);
+ fixture.detectChanges();
+ expect(component.showDebitCardAccountField()).toBeTrue();
+ });
+
+ it('showSingleCreditLineJEField function check', () => {
+ component.cloneSettingsForm.controls.creditCardExportType.patchValue(CorporateCreditCardExpensesObject.JOURNAL_ENTRY);
+ fixture.detectChanges();
+ expect(component.showSingleCreditLineJEField()).toBeTrue();
+ });
+
+ it('resetConfiguraions function check', () => {
+ expect((component as any).resetConfiguraions()).toBeUndefined();
+ fixture.detectChanges();
+ expect(dialogSpy).toHaveBeenCalled();
+ expect(router.navigate).toHaveBeenCalledWith(['/workspaces/onboarding/employee_settings']);
+ });
+
+ it('function check', () => {
+ expect((component as any).setupExportWatchers()).toBeUndefined();
+ expect((component as any).setupExpenseFieldWatcher()).toBeUndefined();
+ });
+
+ it('Save Function check', () => {
+ component.isSaveInProgress = false;
+ component.mappingSettings = [];
+ component.qboExpenseFields = [
+ {
+ "source_field": "COST_CENTER",
+ "destination_field": "CUSTOMER",
+ "import_to_fyle": true,
+ "disable_import_to_fyle": false,
+ "source_placeholder": ""
+ }
+ ];
+ component.chartOfAccountTypesList = [
+ 'Expense', 'Other Expense', 'Fixed Asset', 'Cost of Goods Sold', 'Current Liability', 'Equity',
+ 'Other Current Asset', 'Other Current Liability', 'Long Term Liability', 'Current Asset', 'Income', 'Other Income'
+ ];
+ spyOn(cloneSettingService, 'postCloneSettings').and.callThrough();
+ expect(component.save()).toBeUndefined();
+ fixture.detectChanges();
+ expect(cloneSettingService.postCloneSettings).toHaveBeenCalled();
+ });
+
+ it('navigateToPreviousStep Function check', () => {
+ component.navigateToPreviousStep();
+ expect(routerSpy.navigate).toHaveBeenCalledWith(([`/workspaces/onboarding/qbo_connector`]));
+ });
+
+ it('enableTaxImport Function check', () => {
+ component.enableTaxImport();
+ expect(component.cloneSettingsForm.controls.taxCode.value).toBeTrue();
+ });
+
+ it('enableTaxImport Function check', () => {
+ component.enableTaxImport();
+ expect(component.cloneSettingsForm.controls.taxCode.value).toBeTrue();
+ });
+
+ it('enableVendorAsMerchantImport Function check', () => {
+ component.enableVendorAsMerchantImport();
+ expect(component.cloneSettingsForm.controls.importVendorsAsMerchants.value).toBeTrue();
+ });
+
+ it('disablVendorAsMerchantImport Function check', () => {
+ component.disablVendorAsMerchantImport();
+ expect(component.cloneSettingsForm.controls.importVendorsAsMerchants.value).toBeFalse();
+ });
+
+ it('disableImportCoa Function check', () => {
+ component.disableImportCoa();
+ expect(component.cloneSettingsForm.controls.chartOfAccount.value).toBeFalse();
+ });
+
+ it('restrictExpenseGroupSetting function check', () => {
+ expect((component as any).restrictExpenseGroupSetting('CREDIT CARD PURCHASE')).toBeUndefined();
+ });
+
+ it('enableAccountImport function check', () => {
+ component.enableAccountImport();
+ expect(component.cloneSettingsForm.controls.chartOfAccount.value).toBeTrue();
+ });
+
+ it('setImportFields function check', () => {
+ component.enableAccountImport();
+ expect(component.cloneSettingsForm.controls.chartOfAccount.value).toBeTrue();
+ });
+
+ it('getQboExpenseFields function check', () => {
+ const qboAttributes = ['CUSTOMER'];
+ const mappingSettings = [
+ {
+ "source_field": "COST_CENTER",
+ "destination_field": "CUSTOMER",
+ "import_to_fyle": true,
+ "is_custom": false,
+ "source_placeholder": null
+ }
+ ];
+ const fyleFields = ['COST_CENTER', 'PROJECT'];
+
+ expect((component as any).getQboExpenseFields(qboAttributes, mappingSettings, true, fyleFields));
+ });
+
+ it('setImportFields function check', () => {
+ component.mappingSettings = [];
+
+ const fyleFields = ['COST_CENTER', 'PROJECT'];
+ spyOn(importSettingService, 'getQboExpenseFields').and.returnValue([]);
+ expect((component as any).setImportFields(fyleFields));
+ });
+
+ it('setupEmployeeMappingWatcher function check', () => {
+ component.cloneSettingsForm.controls.employeeMapping.patchValue(EmployeeFieldMapping.VENDOR);
+ expect((component as any).setupEmployeeMappingWatcher()).toBeUndefined();
+ fixture.detectChanges();
+ component.cloneSettingsForm.controls.employeeMapping.patchValue(EmployeeFieldMapping.EMPLOYEE);
+ expect((component as any).setupEmployeeMappingWatcher()).toBeUndefined();
+ });
+
+ it('createReimbursableExportTypeWatcher function check', () => {
+ component.cloneSettingsForm.controls.reimbursableExportType.patchValue(ReimbursableExpensesObject.EXPENSE);
+ expect((component as any).createReimbursableExportTypeWatcher()).toBeUndefined();
+ fixture.detectChanges();
+ component.cloneSettingsForm.controls.reimbursableExportType.patchValue(ReimbursableExpensesObject.BILL);
+ expect((component as any).createReimbursableExportTypeWatcher()).toBeUndefined();
+ });
+
+ it('createCreditCardExportTypeWatcher function check', () => {
+ component.cloneSettingsForm.controls.creditCardExportType.patchValue(CorporateCreditCardExpensesObject.DEBIT_CARD_EXPENSE);
+ expect((component as any).createCreditCardExportTypeWatcher()).toBeUndefined();
+ fixture.detectChanges();
+ component.cloneSettingsForm.controls.employeeMapping.patchValue(CorporateCreditCardExpensesObject.JOURNAL_ENTRY);
+ expect((component as any).createCreditCardExportTypeWatcher()).toBeUndefined();
+ });
+
+ it('openAddemailDialog function check', () => {
+ expect(component.openAddemailDialog()).toBeUndefined();
+ });
+
+ it('showAutoCreateVendorsField function check', () => {
+ expect(component.showAutoCreateVendorsField()).toBeFalse();
+ });
+
+ it('showAutoCreateMerchantsAsVendorsField function check', () => {
+ expect(component.showAutoCreateMerchantsAsVendorsField()).toBeFalse();
+ });
+
+ it('showPaymentSyncField function check', () => {
+ expect(component.showPaymentSyncField()).toBeTrue();
+ });
+
+ it('showImportProducts function check', () => {
+ expect(component.showImportProducts()).toBeTrue();
+ });
+
+ it('showImportVendors function check', () => {
+ expect(component.showImportVendors()).toBeTrue();
+ });
+
+ it('formatememopreview function check', () => {
+ component.memoStructure = memo;
+ fixture.detectChanges();
+ (component as any).formatMemoPreview();
+ expect(component.memoPreviewText.length).toEqual(previewResponse.length);
+ });
+
+ it('generateGroupingLabel function check', () => {
+ component.cloneSettingsForm.controls.reimbursableExportType.patchValue(ReimbursableExpensesObject.EXPENSE);
+ fixture.detectChanges();
+ expect(component.generateGroupingLabel(ExportSource.REIMBURSABLE)).toEqual('How should the expenses be grouped?');
+ });
+
+ it('createMemoStructureWatcher function check', () => {
+ expect((component as any).createMemoStructureWatcher()).toBeUndefined();
+ component.cloneSettingsForm.controls.memoStructure.patchValue(['Integration']);
+ });
+
+ it('setCustomValidatorsAndWatchers function check', () => {
+ expect((component as any).setCustomValidatorsAndWatchers()).toBeUndefined();
+ expect((component as any).setupForm()).toBeUndefined();
+ });
+
+ it('addExpenseField function check', () => {
+ const formOptions = {
+ source_field: 'SOURCE_FIELD',
+ destination_field: 'DESTINATION_FIELD',
+ import_to_fyle: true,
+ disable_import_to_fyle: false,
+ source_placeholder: 'placeholder'
+ };
+
+ const additionalQboExpenseFields = {
+ source_field: 'SOURCE_FIELD_DUPE',
+ destination_field: 'DESTINATION_FIELD_DUPE',
+ import_to_fyle: true,
+ disable_import_to_fyle: false,
+ source_placeholder: 'placeholder'
+ };
+
+ component.additionalQboExpenseFields = [additionalQboExpenseFields];
+ expect((component as any).addExpenseField(formOptions)).toBeUndefined();
+ });
+});
+
+
diff --git a/src/app/integration/onboarding/clone-settings/clone-settings.component.ts b/src/app/integration/onboarding/clone-settings/clone-settings.component.ts
new file mode 100644
index 00000000..bc200565
--- /dev/null
+++ b/src/app/integration/onboarding/clone-settings/clone-settings.component.ts
@@ -0,0 +1,674 @@
+import { Component, OnInit } from '@angular/core';
+import { FormGroup, FormBuilder, Validators, AbstractControl, UntypedFormGroup, FormArray } from '@angular/forms';
+import { forkJoin } from 'rxjs';
+import { CloneSetting, CloneSettingModel } from 'src/app/core/models/configuration/clone-setting.model';
+import { ExportSettingFormOption, NameInJournalEntryOptions } from 'src/app/core/models/configuration/export-setting.model';
+import { DestinationAttribute } from 'src/app/core/models/db/destination-attribute.model';
+import { CloneSettingService } from 'src/app/core/services/configuration/clone-setting.service';
+import { ExportSettingService } from 'src/app/core/services/configuration/export-setting.service';
+import { MatLegacySnackBar as MatSnackBar } from '@angular/material/legacy-snack-bar';
+import { CdkDragDrop, moveItemInArray } from '@angular/cdk/drag-drop';
+
+import { HelperService } from 'src/app/core/services/core/helper.service';
+import { EmployeeFieldMapping, ReimbursableExpensesObject, ClickEvent, OnboardingStep, ProgressPhase, ExpenseGroupingFieldOption, CorporateCreditCardExpensesObject, ExportDateType, MappingDestinationField, SimpleSearchType, SimpleSearchPage, PaymentSyncDirection, QBOField, AutoMapEmployee, ExportSource } from 'src/app/core/models/enum/enum.model';
+import { MappingService } from 'src/app/core/services/misc/mapping.service';
+import { TrackingService } from 'src/app/core/services/integration/tracking.service';
+import { ConfirmationDialog } from 'src/app/core/models/misc/confirmation-dialog.model';
+import { Router } from '@angular/router';
+import { MappingSetting } from 'src/app/core/models/db/mapping-setting.model';
+import { EmployeeSettingFormOption } from 'src/app/core/models/configuration/employee-setting.model';
+import { ExpenseFieldsFormOption } from 'src/app/core/models/configuration/import-setting.model';
+import { RxwebValidators } from '@rxweb/reactive-form-validators';
+import { ImportSettingService } from 'src/app/core/services/configuration/import-setting.service';
+import { ExpenseField } from 'src/app/core/models/misc/expense-field.model';
+import { AdvancedSettingService } from 'src/app/core/services/configuration/advanced-setting.service';
+import { AdvancedSettingFormOption } from 'src/app/core/models/configuration/advanced-setting.model';
+import { WorkspaceScheduleEmailOptions } from 'src/app/core/models/db/workspace-schedule.model';
+import { EmployeeSettingService } from 'src/app/core/services/configuration/employee-setting.service';
+
+
+@Component({
+ selector: 'app-clone-settings',
+ templateUrl: './clone-settings.component.html',
+ styleUrls: ['./clone-settings.component.scss']
+})
+export class CloneSettingsComponent implements OnInit {
+
+ isLoading: boolean = true;
+
+ isSaveInProgress: boolean = false;
+
+ fyleExpenseFields: string[];
+
+ cloneSettingsForm: FormGroup;
+
+ autoMapEmployeeTypes: EmployeeSettingFormOption[] = this.employeeSettingService.getAutoMapEmployeeOptions();
+
+ reimbursableExportOptions: ExportSettingFormOption[];
+
+ reimbursableExpenseGroupingDateOptions: ExportSettingFormOption[] = this.exportSettingService.getReimbursableExpenseGroupingDateOptions();
+
+ employeeFieldMappingOptions: EmployeeSettingFormOption[] = this.employeeSettingService.getEmployeeFieldMappingOptions();
+
+ expenseGroupingFieldOptions: ExportSettingFormOption[] = this.exportSettingService.getReimbursableExpenseGroupingFieldOptions();
+
+ paymentSyncOptions: AdvancedSettingFormOption[] = this.advancedSettingService.getPaymentSyncOptions();
+
+ frequencyIntervals: AdvancedSettingFormOption[] = this.advancedSettingService.getFrequencyIntervals();
+
+ nameInJournalOptions: NameInJournalEntryOptions[] = this.exportSettingService.nameInJournalOptions();
+
+ adminEmails: WorkspaceScheduleEmailOptions[];
+
+ bankAccounts: DestinationAttribute[];
+
+ cccAccounts: DestinationAttribute[];
+
+ accountsPayables: DestinationAttribute[];
+
+ vendors: DestinationAttribute[];
+
+ employeeFieldMapping: EmployeeFieldMapping;
+
+ expenseAccounts: DestinationAttribute[];
+
+ cloneSettings: CloneSetting;
+
+ reimbursableExpenseStateOptions: ExportSettingFormOption[];
+
+ cccExpenseStateOptions: ExportSettingFormOption[];
+
+ cccExpenseExportOptions: ExportSettingFormOption[];
+
+ mappingSettings: MappingSetting[];
+
+ cccExpenseGroupingDateOptions: ExportSettingFormOption[];
+
+ ProgressPhase = ProgressPhase;
+
+ qboExpenseFields: ExpenseFieldsFormOption[];
+
+ additionalQboExpenseFields: ExpenseFieldsFormOption[];
+
+ SimpleSearchPage = SimpleSearchPage;
+
+ SimpleSearchType = SimpleSearchType;
+
+ taxCodes: DestinationAttribute[];
+
+ autoCreateMerchantsAsVendors: boolean;
+
+ exportSource = ExportSource;
+
+ chartOfAccountTypesList: string[] = [
+ 'Expense', 'Other Expense', 'Fixed Asset', 'Cost of Goods Sold', 'Current Liability', 'Equity',
+ 'Other Current Asset', 'Other Current Liability', 'Long Term Liability', 'Current Asset', 'Income', 'Other Income'
+ ];
+
+ defaultMemoFields: string[] = ['employee_email', 'merchant', 'purpose', 'category', 'spent_on', 'report_number', 'expense_link'];
+
+ hoveredIndex: {
+ categoryImport: number,
+ itemsImport: number,
+ vendorsImport: number,
+ expenseFieldImport: number,
+ taxImport: number
+ } = {
+ categoryImport: -1,
+ itemsImport: -1,
+ vendorsImport: -1,
+ expenseFieldImport: -1,
+ taxImport: -1
+ };
+
+ memoStructure: string[] = [];
+
+ memoPreviewText: string = '';
+
+ showNameInJournalOption: boolean = false;
+
+ constructor(
+ private advancedSettingService: AdvancedSettingService,
+ private exportSettingService: ExportSettingService,
+ private importSettingService: ImportSettingService,
+ private employeeSettingService: EmployeeSettingService,
+ public helperService: HelperService,
+ private formBuilder: FormBuilder,
+ private cloneSettingService: CloneSettingService,
+ private mappingService: MappingService,
+ private trackingService: TrackingService,
+ private snackBar: MatSnackBar,
+ private router: Router
+ ) { }
+
+ resetConfiguraions(): void {
+ const data: ConfirmationDialog = {
+ title: 'Are you sure?',
+ contents: `By resetting the configuration, you will be configuring each setting individually from the beginning.
+ Would you like to continue?`,
+ primaryCtaText: 'Yes'
+ };
+ this.trackingService.onClickEvent(ClickEvent.CLONE_SETTINGS_RESET, {page: OnboardingStep.CLONE_SETTINGS});
+
+ this.helperService.openDialogAndSetupRedirection(data, '/workspaces/onboarding/employee_settings');
+ }
+
+ navigateToPreviousStep(): void {
+ this.trackingService.onClickEvent(ClickEvent.CLONE_SETTINGS_BACK, {page: OnboardingStep.CLONE_SETTINGS});
+ this.router.navigate([`/workspaces/onboarding/qbo_connector`]);
+ }
+
+ private formatMemoPreview(): void {
+ const time = Date.now();
+ const today = new Date(time);
+
+ const previewValues: { [key: string]: string } = {
+ employee_email: 'john.doe@acme.com',
+ category: 'Meals and Entertainment',
+ purpose: 'Client Meeting',
+ merchant: 'Pizza Hut',
+ report_number: 'C/2021/12/R/1',
+ spent_on: today.toLocaleDateString(),
+ expense_link: 'https://app.fylehq.com/app/main/#/enterprise/view_expense/'
+ };
+
+ this.memoPreviewText = '';
+ this.memoStructure.forEach((field, index) => {
+ if (field in previewValues) {
+ this.memoPreviewText += previewValues[field];
+ if (index + 1 !== this.memoStructure.length) {
+ this.memoPreviewText = this.memoPreviewText + ' - ';
+ }
+ }
+ });
+ }
+
+ drop(event: CdkDragDrop) {
+ moveItemInArray(this.defaultMemoFields, event.previousIndex, event.currentIndex);
+ const selectedMemoFields = this.defaultMemoFields.filter(memoOption => this.cloneSettingsForm.value.memoStructure.indexOf(memoOption) !== -1);
+ const memoStructure = selectedMemoFields ? selectedMemoFields : this.defaultMemoFields;
+ this.memoStructure = memoStructure;
+ this.formatMemoPreview();
+ }
+
+ save(): void {
+ if (this.cloneSettingsForm.valid) {
+ this.isSaveInProgress = true;
+ const customMappingSettings = this.mappingSettings.filter(setting => !setting.import_to_fyle);
+ const cloneSettingPayload = CloneSettingModel.constructPayload(this.cloneSettingsForm, customMappingSettings);
+
+ this.cloneSettingService.postCloneSettings(cloneSettingPayload).subscribe((response) => {
+ this.isSaveInProgress = false;
+ this.snackBar.open('Cloned settings successfully');
+ this.router.navigate([`/workspaces/onboarding/done`]);
+ }, () => {
+ this.isSaveInProgress = false;
+ this.snackBar.open('Failed to clone settings');
+ });
+ }
+ }
+
+ getExportType(exportType: ReimbursableExpensesObject | CorporateCreditCardExpensesObject): string {
+ const lowerCaseWord = exportType.toLowerCase();
+ return lowerCaseWord.charAt(0).toUpperCase() + lowerCaseWord.slice(1);
+ }
+
+ generateGroupingLabel(exportSource: ExportSource): string {
+ let exportType: ReimbursableExpensesObject | CorporateCreditCardExpensesObject;
+ if (exportSource === ExportSource.REIMBURSABLE) {
+ exportType = this.cloneSettingsForm.value.reimbursableExportType;
+ } else {
+ exportType = this.cloneSettingsForm.value.creditCardExportType;
+ }
+
+ if (exportType === ReimbursableExpensesObject.EXPENSE) {
+ return 'How should the expenses be grouped?';
+ }
+ return `How should the expense in ${this.getExportType(exportType)} be grouped?`;
+ }
+
+ private setCreditCardExpenseGroupingDateOptions(creditCardExportGroup: ExpenseGroupingFieldOption): void {
+ if (creditCardExportGroup === ExpenseGroupingFieldOption.EXPENSE_ID) {
+ this.cccExpenseGroupingDateOptions = this.reimbursableExpenseGroupingDateOptions.concat([{
+ label: 'Posted Date',
+ value: ExportDateType.POSTED_AT
+ }]);
+ } else {
+ this.cccExpenseGroupingDateOptions = this.reimbursableExpenseGroupingDateOptions.concat();
+ }
+ }
+
+ showImportVendors(): boolean {
+ return !this.cloneSettingsForm.value.autoCreateMerchantsAsVendors;
+ }
+
+ showImportProducts(): boolean {
+ return this.cloneSettingsForm.controls.reimbursableExportType.value !== 'JOURNAL_ENTRY' && this.cloneSettingsForm.controls.creditCardExportType.value !== 'JOURNAL ENTRY';
+ }
+
+ showExpenseAccountField(): boolean {
+ return this.cloneSettingsForm.controls.reimbursableExportType.value === ReimbursableExpensesObject.EXPENSE;
+ }
+
+ showBankAccountField(): boolean {
+ return this.cloneSettingsForm.value.employeeMapping === EmployeeFieldMapping.EMPLOYEE && this.cloneSettingsForm.controls.reimbursableExportType.value && this.cloneSettingsForm.controls.reimbursableExportType.value !== ReimbursableExpensesObject.EXPENSE;
+ }
+
+ showReimbursableAccountsPayableField(): boolean {
+ return (this.cloneSettingsForm.controls.reimbursableExportType.value === ReimbursableExpensesObject.BILL) || (this.cloneSettingsForm.controls.reimbursableExportType.value === ReimbursableExpensesObject.JOURNAL_ENTRY && this.cloneSettingsForm.value.employeeMapping === EmployeeFieldMapping.VENDOR);
+ }
+
+ showCreditCardAccountField(): boolean {
+ return this.cloneSettingsForm.controls.creditCardExportType.value && this.cloneSettingsForm.controls.creditCardExportType.value !== CorporateCreditCardExpensesObject.BILL && this.cloneSettingsForm.controls.creditCardExportType.value !== CorporateCreditCardExpensesObject.DEBIT_CARD_EXPENSE;
+ }
+
+ showDebitCardAccountField(): boolean {
+ return this.cloneSettingsForm.controls.creditCardExportType.value && this.cloneSettingsForm.controls.creditCardExportType.value === CorporateCreditCardExpensesObject.DEBIT_CARD_EXPENSE;
+ }
+
+ showDefaultCreditCardVendorField(): boolean {
+ return this.cloneSettingsForm.controls.creditCardExportType.value === CorporateCreditCardExpensesObject.BILL;
+ }
+
+ showCCCAccountsPayableField(): boolean {
+ return this.cloneSettingsForm.controls.creditCardExportType.value === CorporateCreditCardExpensesObject.BILL;
+ }
+
+ showSingleCreditLineJEField(): boolean {
+ return this.cloneSettingsForm.controls.reimbursableExportType.value === ReimbursableExpensesObject.JOURNAL_ENTRY || this.cloneSettingsForm.controls.creditCardExportType.value === CorporateCreditCardExpensesObject.JOURNAL_ENTRY;
+ }
+
+ showAutoCreateVendorsField(): boolean {
+ return this.cloneSettingsForm.controls.employeeMapping.value === EmployeeFieldMapping.VENDOR && this.cloneSettingsForm.controls.autoMapEmployee.value !== null && this.cloneSettingsForm.controls.autoMapEmployee.value !== AutoMapEmployee.EMPLOYEE_CODE;
+ }
+
+ showAutoCreateMerchantsAsVendorsField(): boolean {
+ return !this.cloneSettingsForm.controls.importVendorsAsMerchants.value && (this.cloneSettingsForm.controls.creditCardExportType.value === CorporateCreditCardExpensesObject.CREDIT_CARD_PURCHASE || this.cloneSettingsForm.controls.creditCardExportType.value === CorporateCreditCardExpensesObject.DEBIT_CARD_EXPENSE);
+ }
+
+ showPaymentSyncField(): boolean {
+ return this.cloneSettingsForm.controls.reimbursableExportType.value === ReimbursableExpensesObject.BILL;
+ }
+
+ private restrictExpenseGroupSetting(creditCardExportType: string | null) : void {
+ if (creditCardExportType === CorporateCreditCardExpensesObject.CREDIT_CARD_PURCHASE || creditCardExportType === CorporateCreditCardExpensesObject.DEBIT_CARD_EXPENSE) {
+ this.cloneSettingsForm.controls.creditCardExportGroup.setValue(ExpenseGroupingFieldOption.EXPENSE_ID);
+ this.cloneSettingsForm.controls.creditCardExportGroup.disable();
+
+ this.cccExpenseGroupingDateOptions = [{
+ label: 'Posted Date',
+ value: ExportDateType.POSTED_AT
+ },
+ {
+ label: 'Spend Date',
+ value: ExportDateType.SPENT_AT
+ }];
+ } else {
+ this.cloneSettingsForm.controls.creditCardExportGroup.enable();
+ this.setCreditCardExpenseGroupingDateOptions(this.cloneSettingsForm.controls.creditCardExportGroup.value);
+ }
+ }
+
+ private createCreditCardExportTypeWatcher(): void {
+ this.restrictExpenseGroupSetting(this.cloneSettingsForm.controls.creditCardExpense.value);
+ this.cloneSettingsForm.controls.creditCardExportType.valueChanges.subscribe((creditCardExportType: string) => {
+ this.exportSettingService.setGeneralMappingsValidator(this.cloneSettingsForm);
+ this.restrictExpenseGroupSetting(creditCardExportType);
+ this.showNameInJournalOption = creditCardExportType === CorporateCreditCardExpensesObject.JOURNAL_ENTRY ? true : false;
+ });
+ }
+
+ private createReimbursableExportTypeWatcher(): void {
+ this.cloneSettingsForm.controls.reimbursableExportType.valueChanges.subscribe(() => {
+ this.exportSettingService.setGeneralMappingsValidator(this.cloneSettingsForm);
+ });
+ }
+
+ private createReimbursableExportGroupWatcher(): void {
+ this.cloneSettingsForm.controls.reimbursableExportGroup.valueChanges.subscribe((reimbursableExportGroup: ExpenseGroupingFieldOption) => {
+ if (reimbursableExportGroup === ExpenseGroupingFieldOption.EXPENSE_ID) {
+ this.reimbursableExpenseGroupingDateOptions.pop();
+ } else {
+ if (this.reimbursableExpenseGroupingDateOptions.length !== 5) {
+ this.reimbursableExpenseGroupingDateOptions.push({
+ label: 'Last Spend Date',
+ value: ExportDateType.LAST_SPENT_AT
+ });
+ }
+ }
+ });
+ }
+
+ private createCreditCardExportGroupWatcher(): void {
+ this.cloneSettingsForm.controls.creditCardExportGroup.valueChanges.subscribe((creditCardExportGroup: ExpenseGroupingFieldOption) => {
+ if (creditCardExportGroup && creditCardExportGroup === ExpenseGroupingFieldOption.EXPENSE_ID) {
+ this.cccExpenseGroupingDateOptions = this.cccExpenseGroupingDateOptions.filter((option) => {
+ return option.value !== ExportDateType.LAST_SPENT_AT;
+ });
+ this.setCreditCardExpenseGroupingDateOptions(creditCardExportGroup);
+ } else {
+ const lastSpentAt = this.cccExpenseGroupingDateOptions.filter((option) => {
+ return option.value === ExportDateType.LAST_SPENT_AT;
+ });
+ if (!lastSpentAt.length) {
+ this.cccExpenseGroupingDateOptions.push({
+ label: 'Last Spend Date',
+ value: ExportDateType.LAST_SPENT_AT
+ });
+ }
+ this.setCreditCardExpenseGroupingDateOptions(creditCardExportGroup);
+ }
+ });
+ }
+
+ private setupExportWatchers(): void {
+ this.cloneSettingsForm?.controls.reimbursableExpense?.setValidators((this.exportSettingService.exportSelectionValidator(this.cloneSettingsForm, true)));
+ this.cloneSettingsForm?.controls.creditCardExpense?.setValidators(this.exportSettingService.exportSelectionValidator(this.cloneSettingsForm, true));
+ }
+
+ private setupExpenseFieldWatcher(): void {
+ this.importSettingService.patchExpenseFieldEmitter.subscribe((expenseField) => {
+ if (expenseField.addSourceField) {
+ this.fyleExpenseFields.push(expenseField.source_field);
+ }
+ this.expenseFields.controls.filter(field => field.value.destination_field === expenseField.destination_field)[0].patchValue(expenseField);
+ });
+ }
+
+ private setupAdditionalEmailsWatcher(): void {
+ this.advancedSettingService.patchAdminEmailsEmitter.subscribe((additionalEmails) => {
+ this.adminEmails = additionalEmails;
+ });
+ }
+
+ private setupEmployeeMappingWatcher(): void {
+ this.cloneSettingsForm.controls.employeeMapping.valueChanges.subscribe((employeeMapping: EmployeeFieldMapping) => {
+ this.reimbursableExportOptions = this.exportSettingService.getReimbursableExportTypeOptions(employeeMapping);
+ });
+ }
+
+ disableImportCoa(): void {
+ this.cloneSettingsForm.controls.chartOfAccount.setValue(false);
+ }
+
+ disableImportItems(): void {
+ this.cloneSettingsForm.controls.importItems.setValue(false);
+ }
+
+ enableItemsImport(): void {
+ this.cloneSettingsForm.controls.importItems.setValue(true);
+ }
+
+ enableVendorAsMerchantImport(): void {
+ this.cloneSettingsForm.controls.importVendorsAsMerchants.setValue(true);
+ }
+
+ disablVendorAsMerchantImport(): void {
+ this.cloneSettingsForm.controls.importVendorsAsMerchants.setValue(false);
+ }
+
+ disableImportTax(): void {
+ this.cloneSettingsForm.controls.taxCode.setValue(false);
+ this.cloneSettingsForm.controls.defaultTaxCode.clearValidators();
+ this.cloneSettingsForm.controls.defaultTaxCode.setValue(null);
+ }
+
+ enableTaxImport(): void {
+ this.cloneSettingsForm.controls.taxCode.setValue(true);
+ this.cloneSettingsForm.controls.defaultTaxCode.setValidators(Validators.required);
+ }
+
+ enableAccountImport(): void {
+ this.cloneSettingsForm.controls.chartOfAccount.setValue(true);
+ }
+
+ private createMemoStructureWatcher(): void {
+ this.formatMemoPreview();
+ this.cloneSettingsForm.controls.memoStructure.valueChanges.subscribe((memoChanges) => {
+ this.memoStructure = memoChanges;
+ this.formatMemoPreview();
+ });
+ }
+
+ private setCustomValidatorsAndWatchers(): void {
+
+ this.createMemoStructureWatcher();
+
+ this.exportSettingService.createReimbursableExpenseWatcher(this.cloneSettingsForm, this.cloneSettings.export_settings);
+ this.exportSettingService.createCreditCardExpenseWatcher(this.cloneSettingsForm, this.cloneSettings.export_settings);
+ this.exportSettingService.setGeneralMappingsValidator(this.cloneSettingsForm);
+
+ // Export select fields
+ this.createReimbursableExportTypeWatcher();
+ this.createCreditCardExportTypeWatcher();
+
+ // Grouping fields
+ this.createReimbursableExportGroupWatcher();
+ this.createCreditCardExportGroupWatcher();
+
+ this.setupExportWatchers();
+ this.setupEmployeeMappingWatcher();
+
+ this.setCreditCardExpenseGroupingDateOptions(this.cloneSettingsForm.controls.creditCardExportGroup.value);
+ this.setupExpenseFieldWatcher();
+
+ this.setupAdditionalEmailsWatcher();
+ }
+
+ createChartOfAccountField(type: string): UntypedFormGroup {
+ return this.formBuilder.group({
+ enabled: [this.cloneSettings.import_settings.workspace_general_settings.charts_of_accounts.includes(type) || type === 'Expense' ? true : false],
+ name: [type]
+ });
+ }
+
+ openAddemailDialog(): void {
+ this.advancedSettingService.openAddemailDialog(this.cloneSettingsForm, this.adminEmails);
+ }
+
+ get chartOfAccountTypes() {
+ return this.cloneSettingsForm.get('chartOfAccountTypes') as FormArray;
+ }
+
+ get expenseFields() {
+ return this.cloneSettingsForm.get('expenseFields') as FormArray;
+ }
+
+ createExpenseField(destinationType: string): void {
+ this.importSettingService.createExpenseField(destinationType, this.mappingSettings);
+ }
+
+ addExpenseField(field: ExpenseFieldsFormOption): void {
+ this.expenseFields.push(this.formBuilder.group({
+ source_field: [field.source_field, Validators.compose([RxwebValidators.unique(), Validators.required])],
+ destination_field: [field.destination_field.toUpperCase()],
+ disable_import_to_fyle: [field.disable_import_to_fyle],
+ import_to_fyle: [field.import_to_fyle],
+ source_placeholder: ['']
+ }));
+ this.expenseFields.markAllAsTouched();
+ this.additionalQboExpenseFields = this.additionalQboExpenseFields.filter((expenseField) => expenseField.destination_field !== field.destination_field);
+ }
+
+ deleteExpenseField(index: number, expenseField: AbstractControl): void {
+ this.expenseFields.removeAt(index);
+ const additionalField = {
+ source_field: '',
+ destination_field: expenseField.value.destination_field,
+ disable_import_to_fyle: false,
+ import_to_fyle: false,
+ source_placeholder: ''
+ };
+ this.additionalQboExpenseFields.push(additionalField);
+ }
+
+
+ private setupForm(): void {
+ const chartOfAccountTypeFormArray = this.chartOfAccountTypesList.map((type) => this.createChartOfAccountField(type));
+
+ const expenseFieldsFormArray = this.importSettingService.getExpenseFieldsFormArray(this.qboExpenseFields, false);
+
+ let paymentSync = '';
+ if (this.cloneSettings.advanced_configurations.workspace_general_settings.sync_fyle_to_qbo_payments) {
+ paymentSync = PaymentSyncDirection.FYLE_TO_QBO;
+ } else if (this.cloneSettings.advanced_configurations.workspace_general_settings.sync_qbo_to_fyle_payments) {
+ paymentSync = PaymentSyncDirection.QBO_TO_FYLE;
+ }
+
+ this.memoStructure = this.cloneSettings.advanced_configurations.workspace_general_settings.memo_structure;
+
+ this.cloneSettingsForm = this.formBuilder.group({
+ // Employee Mapping
+ employeeMapping: [this.cloneSettings.employee_mappings.workspace_general_settings?.employee_field_mapping, Validators.required],
+ autoMapEmployee: [this.cloneSettings.employee_mappings.workspace_general_settings?.auto_map_employees],
+
+ // Export Settings
+ reimbursableExpense: [this.cloneSettings.export_settings.workspace_general_settings?.reimbursable_expenses_object ? true : false],
+ reimbursableExportDate: [this.cloneSettings.export_settings.expense_group_settings?.reimbursable_export_date_type],
+ expenseState: [this.cloneSettings.export_settings.expense_group_settings?.expense_state],
+ reimbursableExportGroup: [this.exportSettingService.getExportGroup(this.cloneSettings.export_settings.expense_group_settings?.reimbursable_expense_group_fields)],
+ reimbursableExportType: [this.cloneSettings.export_settings.workspace_general_settings?.reimbursable_expenses_object],
+
+ creditCardExpense: [this.cloneSettings.export_settings.workspace_general_settings?.corporate_credit_card_expenses_object ? true : false],
+ creditCardExportDate: [this.cloneSettings.export_settings.expense_group_settings?.ccc_export_date_type],
+ cccExpenseState: [this.cloneSettings.export_settings.expense_group_settings?.ccc_expense_state],
+ creditCardExportGroup: [this.exportSettingService.getExportGroup(this.cloneSettings.export_settings.expense_group_settings?.corporate_credit_card_expense_group_fields)],
+ creditCardExportType: [this.cloneSettings.export_settings.workspace_general_settings?.corporate_credit_card_expenses_object],
+ nameInJournalEntry: [this.cloneSettings.export_settings.workspace_general_settings.name_in_journal_entry],
+
+ bankAccount: [this.cloneSettings.export_settings.general_mappings?.bank_account?.id ? this.cloneSettings.export_settings.general_mappings.bank_account : null],
+ qboExpenseAccount: [this.cloneSettings.export_settings.general_mappings?.qbo_expense_account?.id ? this.cloneSettings.export_settings.general_mappings.qbo_expense_account : null],
+ defaultCCCAccount: [this.cloneSettings.export_settings.general_mappings?.default_ccc_account?.id ? this.cloneSettings.export_settings.general_mappings.default_ccc_account : null],
+ accountsPayable: [this.cloneSettings.export_settings.general_mappings?.accounts_payable?.id ? this.cloneSettings.export_settings.general_mappings.accounts_payable : null],
+ defaultCreditCardVendor: [this.cloneSettings.export_settings.general_mappings?.default_ccc_vendor?.id ? this.cloneSettings.export_settings.general_mappings.default_ccc_vendor : null],
+ defaultDebitCardAccount: [this.cloneSettings.export_settings.general_mappings?.default_debit_card_account?.id ? this.cloneSettings.export_settings.general_mappings.default_debit_card_account : null],
+ searchOption: [],
+
+ // Import Settings
+ chartOfAccount: [this.cloneSettings.import_settings.workspace_general_settings.import_categories],
+ importItems: [this.cloneSettings.import_settings.workspace_general_settings.import_items],
+ chartOfAccountTypes: this.formBuilder.array(chartOfAccountTypeFormArray),
+ expenseFields: this.formBuilder.array(expenseFieldsFormArray),
+ taxCode: [this.cloneSettings.import_settings.workspace_general_settings.import_tax_codes],
+ defaultTaxCode: [this.cloneSettings.import_settings.general_mappings?.default_tax_code?.id ? this.cloneSettings.import_settings.general_mappings.default_tax_code : null],
+ importVendorsAsMerchants: [this.cloneSettings.import_settings.workspace_general_settings.import_vendors_as_merchants],
+
+ // Advanced Settings
+ paymentSync: [paymentSync],
+ billPaymentAccount: [this.cloneSettings.advanced_configurations.general_mappings?.bill_payment_account],
+ exportSchedule: [this.cloneSettings.advanced_configurations.workspace_schedules?.enabled ? this.cloneSettings.advanced_configurations.workspace_schedules.interval_hours : false],
+ exportScheduleFrequency: [this.cloneSettings.advanced_configurations.workspace_schedules?.enabled ? this.cloneSettings.advanced_configurations.workspace_schedules.interval_hours : null],
+ emails: [this.cloneSettings.advanced_configurations.workspace_schedules?.emails_selected ? this.cloneSettings.advanced_configurations.workspace_schedules?.emails_selected : []],
+ addedEmail: [],
+ changeAccountingPeriod: [this.cloneSettings.advanced_configurations.workspace_general_settings.change_accounting_period],
+ autoCreateVendors: [this.cloneSettings.advanced_configurations.workspace_general_settings.auto_create_destination_entity],
+ autoCreateMerchantsAsVendors: [this.cloneSettings.advanced_configurations.workspace_general_settings.auto_create_merchants_as_vendors ? this.cloneSettings.advanced_configurations.workspace_general_settings.auto_create_merchants_as_vendors : false],
+ singleCreditLineJE: [this.cloneSettings.advanced_configurations.workspace_general_settings.je_single_credit_line],
+ memoStructure: [this.cloneSettings.advanced_configurations.workspace_general_settings.memo_structure]
+
+ });
+
+ this.setCustomValidatorsAndWatchers();
+
+ this.cloneSettingsForm.markAllAsTouched();
+
+ this.isLoading = false;
+ }
+
+ private getQboExpenseFields(qboAttributes: string[], mappingSettings: MappingSetting[], isCloneSettings: boolean = false, fyleFields: string[] = []): ExpenseFieldsFormOption[] {
+ return qboAttributes.map(attribute => {
+ const mappingSetting = mappingSettings.filter((mappingSetting: MappingSetting) => {
+ if (mappingSetting.destination_field.toUpperCase() === attribute) {
+ if (isCloneSettings) {
+ return fyleFields.includes(mappingSetting.source_field.toUpperCase()) ? mappingSetting : false;
+ }
+ return mappingSetting;
+ }
+ return false;
+ });
+
+ return {
+ source_field: mappingSetting.length > 0 ? mappingSetting[0].source_field : '',
+ destination_field: attribute,
+ import_to_fyle: mappingSetting.length > 0 ? mappingSetting[0].import_to_fyle : false,
+ disable_import_to_fyle: false,
+ source_placeholder: ''
+ };
+ });
+ }
+
+ private setImportFields(fyleFields: ExpenseField[]): void {
+ this.fyleExpenseFields = fyleFields.map(field => field.attribute_type);
+ // Remove custom mapped Fyle options
+ const customMappedFyleFields = this.mappingSettings.filter(setting => !setting.import_to_fyle).map(setting => setting.source_field);
+ const customMappedQuickbooksFields = this.mappingSettings.filter(setting => !setting.import_to_fyle).map(setting => setting.destination_field);
+ const importedQboFields = this.cloneSettings.import_settings.mapping_settings.filter(setting => setting.import_to_fyle).map(setting => setting.destination_field);
+
+ if (customMappedFyleFields.length) {
+ this.fyleExpenseFields = this.fyleExpenseFields.filter(field => !customMappedFyleFields.includes(field));
+ }
+
+ const qboFields = [
+ {attribute_type: MappingDestinationField.CLASS, display_name: 'Class'},
+ {attribute_type: MappingDestinationField.DEPARTMENT, display_name: 'Department'},
+ {attribute_type: MappingDestinationField.CUSTOMER, display_name: 'Customer'}
+ ];
+
+ // Remove custom mapped Quickbooks fields
+ const qboAttributes = qboFields.filter(
+ field => !customMappedQuickbooksFields.includes(field.attribute_type)
+ );
+
+ this.qboExpenseFields = this.getQboExpenseFields(importedQboFields, this.cloneSettings.import_settings.mapping_settings, true, this.fyleExpenseFields);
+ const allExpenseFields = this.importSettingService.getQboExpenseFields(qboAttributes, this.cloneSettings.import_settings.mapping_settings, true, this.fyleExpenseFields);
+
+ this.additionalQboExpenseFields = allExpenseFields.filter((field) => {
+ return !this.qboExpenseFields.some((qboField) => qboField.destination_field.toUpperCase() === field.destination_field.toUpperCase());
+ });
+ }
+
+
+ private setupPage(): void {
+ const destinationAttributes = ['BANK_ACCOUNT', 'CREDIT_CARD_ACCOUNT', 'ACCOUNTS_PAYABLE', 'VENDOR'];
+
+ forkJoin([
+ this.cloneSettingService.getCloneSettings(),
+ this.mappingService.getGroupedQBODestinationAttributes(destinationAttributes),
+ this.mappingService.getMappingSettings(),
+ this.mappingService.getFyleExpenseFields(),
+ this.advancedSettingService.getWorkspaceAdmins(),
+ this.mappingService.getQBODestinationAttributes(QBOField.TAX_CODE)
+ ]).subscribe(responses => {
+ this.cloneSettings = responses[0];
+ this.fyleExpenseFields = responses[3].map(field => field.attribute_type);
+
+ this.adminEmails = this.cloneSettings.advanced_configurations.workspace_schedules?.additional_email_options ? this.cloneSettings.advanced_configurations.workspace_schedules?.additional_email_options.concat(responses[4]) : responses[4];
+ this.employeeFieldMapping = this.cloneSettings.employee_mappings.workspace_general_settings.employee_field_mapping;
+ this.mappingSettings = responses[2].results;
+
+ this.autoCreateMerchantsAsVendors = responses[0].advanced_configurations.workspace_general_settings.auto_create_merchants_as_vendors;
+ this.bankAccounts = responses[1].BANK_ACCOUNT;
+ this.cccAccounts = responses[1].CREDIT_CARD_ACCOUNT;
+ this.accountsPayables = responses[1].ACCOUNTS_PAYABLE;
+ this.vendors = responses[1].VENDOR;
+ this.expenseAccounts = this.bankAccounts.concat(this.cccAccounts);
+ this.showNameInJournalOption = this.cloneSettings.export_settings.workspace_general_settings?.corporate_credit_card_expenses_object === CorporateCreditCardExpensesObject.JOURNAL_ENTRY ? true : false;
+
+ this.reimbursableExportOptions = this.exportSettingService.getReimbursableExportTypeOptions(EmployeeFieldMapping.EMPLOYEE);
+ this.cccExpenseExportOptions = this.exportSettingService.getcreditCardExportTypes();
+
+ this.reimbursableExpenseStateOptions = this.exportSettingService.getReimbursableExpenseStateOptions(this.cloneSettings.export_settings.workspace_general_settings.is_simplify_report_closure_enabled);
+ this.cccExpenseStateOptions = this.exportSettingService.getCCCExpenseStateOptions(this.cloneSettings.export_settings.workspace_general_settings.is_simplify_report_closure_enabled);
+ this.reimbursableExportOptions = this.exportSettingService.getReimbursableExportTypeOptions(this.cloneSettings.employee_mappings.workspace_general_settings.employee_field_mapping);
+
+ this.setImportFields(responses[3]);
+ this.taxCodes = responses[5];
+
+ this.setupForm();
+ });
+ }
+
+ ngOnInit(): void {
+ this.setupPage();
+ }
+}
diff --git a/src/app/integration/onboarding/clone-settings/clone-settings.fixture.ts b/src/app/integration/onboarding/clone-settings/clone-settings.fixture.ts
new file mode 100644
index 00000000..f22471dc
--- /dev/null
+++ b/src/app/integration/onboarding/clone-settings/clone-settings.fixture.ts
@@ -0,0 +1,153 @@
+import { CloneSetting, CloneSettingExist, CloneSettingPost } from "src/app/core/models/configuration/clone-setting.model";
+import { GroupedDestinationAttribute } from "src/app/core/models/db/destination-attribute.model";
+import {AutoMapEmployee, CCCExpenseState, CorporateCreditCardExpensesObject, EmployeeFieldMapping, ExpenseState, ExportDateType, MappingDestinationField, MappingSourceField, NameInJournalEntry, ReimbursableExpensesObject } from "src/app/core/models/enum/enum.model";
+
+export const mockCloneSettingExist: CloneSettingExist = {
+ is_available: true,
+ workspace_name: 'Fyle for Ashwin'
+};
+
+export const mockCloneSettingsGet: CloneSetting = {
+ workspace_id: 1,
+ export_settings: {
+ expense_group_settings: {
+ reimbursable_expense_group_fields: null,
+ corporate_credit_card_expense_group_fields: null,
+ expense_state: ExpenseState.PAID,
+ reimbursable_export_date_type: null,
+ ccc_expense_state: CCCExpenseState.PAID,
+ ccc_export_date_type: null
+ },
+ workspace_general_settings: {
+ reimbursable_expenses_object: ReimbursableExpensesObject.BILL,
+ name_in_journal_entry: NameInJournalEntry.EMPLOYEE,
+ corporate_credit_card_expenses_object: null,
+ is_simplify_report_closure_enabled: false
+ },
+ general_mappings: {
+ accounts_payable: { name: 'fyle', id: "1" },
+ bank_account: { name: 'fyle', id: "1" },
+ qbo_expense_account: { name: 'fyle', id: "1" },
+ default_ccc_account: { name: 'fyle', id: "1" },
+ default_ccc_vendor: { name: 'fyle', id: "1" },
+ default_debit_card_account: { name: 'fyle', id: "1" }
+ },
+ workspace_id: 1
+ },
+ import_settings: {
+ general_mappings: {
+ id: 1,
+ created_at: new Date(),
+ updated_at: new Date(),
+ workspace: 1,
+ accounts_payable: { name: 'fyle', id: "1" },
+ bank_account: { name: 'fyle', id: "1" },
+ qbo_expense_account: { name: 'fyle', id: "1" },
+ default_ccc_account: { name: 'fyle', id: "1" },
+ default_ccc_vendor: { name: 'fyle', id: "1" },
+ default_debit_card_account: { name: 'fyle', id: "1" },
+ default_tax_code: { name: 'fyle', id: "1" },
+ bill_payment_account: { name: 'fyle', id: "1" }
+ },
+ mapping_settings: [{
+ id: 1,
+ created_at: new Date(),
+ updated_at: new Date(),
+ workspace: 1,
+ source_field: MappingSourceField.TAX_GROUP,
+ destination_field: MappingSourceField.PROJECT,
+ import_to_fyle: true,
+ is_custom: true,
+ source_placeholder: null
+ },
+ {
+ id: 2,
+ created_at: new Date(),
+ updated_at: new Date(),
+ workspace: 1,
+ source_field: 'CUSTOM_FIELD',
+ destination_field: MappingDestinationField.CLASS,
+ import_to_fyle: false,
+ is_custom: true,
+ source_placeholder: null
+ }],
+ workspace_general_settings: {
+ auto_create_destination_entity: true,
+ auto_create_merchants_as_vendors: true,
+ memo_structure: [],
+ change_accounting_period: true,
+ charts_of_accounts: ['Expense'],
+ created_at: new Date("2022-04-27T11:07:17.694377Z"),
+ id: 1,
+ employee_field_mapping: EmployeeFieldMapping.EMPLOYEE,
+ import_vendors_as_merchants: true,
+ import_items: true,
+ je_single_credit_line: false,
+ import_categories: false,
+ import_projects: false,
+ import_tax_codes: false,
+ skip_cards_mapping: false,
+ sync_fyle_to_qbo_payments: false,
+ sync_qbo_to_fyle_payments: false,
+ updated_at: new Date("2022-04-28T12:48:39.150177Z"),
+ workspace: 1,
+ reimbursable_expenses_object: null,
+ corporate_credit_card_expenses_object: CorporateCreditCardExpensesObject.CREDIT_CARD_PURCHASE,
+ name_in_journal_entry: NameInJournalEntry.EMPLOYEE,
+ auto_map_employees: AutoMapEmployee.EMAIL,
+ is_simplify_report_closure_enabled: true
+ },
+ workspace_id: 1
+ },
+ advanced_configurations: {
+ workspace_general_settings: {
+ sync_fyle_to_qbo_payments: true,
+ sync_qbo_to_fyle_payments: false,
+ auto_create_destination_entity: true,
+ change_accounting_period: true,
+ je_single_credit_line: true,
+ auto_create_merchants_as_vendors: true,
+ memo_structure: []
+ },
+ general_mappings: {
+ bill_payment_account: {id: '1', name: 'Fyle'}
+ },
+ workspace_schedules: {
+ enabled: true,
+ interval_hours: 10,
+ emails_selected: [],
+ additional_email_options: []
+ },
+ workspace_id: 1
+ },
+ employee_mappings: {
+ workspace_id: 1,
+ workspace_general_settings: {
+ auto_map_employees: AutoMapEmployee.NAME,
+ employee_field_mapping: EmployeeFieldMapping.EMPLOYEE
+ }
+ }
+ };
+
+export const mockGroupedDestinationAttribtues: GroupedDestinationAttribute = {
+ BANK_ACCOUNT: [{
+ id: 3,
+ attribute_type: 'BANK_ACCOUNT',
+ display_name: "string",
+ value: "Fyle",
+ destination_id: "1",
+ active: true,
+ created_at: new Date(),
+ updated_at: new Date(),
+ workspace: 2,
+ detail: {
+ email: 'String',
+ fully_qualified_name: 'string'
+ }
+ }],
+ TAX_CODE: [],
+ CREDIT_CARD_ACCOUNT: [],
+ ACCOUNTS_PAYABLE: [],
+ VENDOR: [],
+ ACCOUNT: []
+};
diff --git a/src/app/integration/onboarding/onboarding-routing.module.ts b/src/app/integration/onboarding/onboarding-routing.module.ts
index 3b250081..fc7e7688 100644
--- a/src/app/integration/onboarding/onboarding-routing.module.ts
+++ b/src/app/integration/onboarding/onboarding-routing.module.ts
@@ -9,6 +9,7 @@ import { OnboardingImportSettingsComponent } from './onboarding-import-settings/
import { OnboardingLandingComponent } from './onboarding-landing/onboarding-landing.component';
import { OnboardingQboConnectorComponent } from './onboarding-qbo-connector/onboarding-qbo-connector.component';
import { OnboardingComponent } from './onboarding.component';
+import { CloneSettingsComponent } from './clone-settings/clone-settings.component';
const routes: Routes = [
@@ -20,6 +21,11 @@ const routes: Routes = [
path: '',
component: OnboardingComponent,
children: [
+ {
+ path: 'clone_settings',
+ component: CloneSettingsComponent,
+ canActivate: [WorkspacesGuard]
+ },
{
path: 'export_settings',
component: OnboardingExportSettingsComponent,
diff --git a/src/app/integration/onboarding/onboarding.module.ts b/src/app/integration/onboarding/onboarding.module.ts
index 3d3e83e0..20cf0369 100644
--- a/src/app/integration/onboarding/onboarding.module.ts
+++ b/src/app/integration/onboarding/onboarding.module.ts
@@ -10,8 +10,11 @@ import { MatLegacySelectModule as MatSelectModule } from '@angular/material/lega
import { MatLegacySlideToggleModule as MatSlideToggleModule } from '@angular/material/legacy-slide-toggle';
import { MatLegacyCheckboxModule as MatCheckboxModule } from '@angular/material/legacy-checkbox';
import { MatLegacyDialogModule as MatDialogModule } from '@angular/material/legacy-dialog';
-
+import { MatMenuModule } from '@angular/material/menu';
+import { MatLegacyTooltipModule as MatTooltipModule } from '@angular/material/legacy-tooltip';
+import { MatLegacyProgressSpinnerModule as MatProgressSpinnerModule } from '@angular/material/legacy-progress-spinner';
import { SharedModule } from 'src/app/shared/shared.module';
+import { DragDropModule } from '@angular/cdk/drag-drop';
// Components
import { OnboardingExportSettingsComponent } from './onboarding-export-settings/onboarding-export-settings.component';
@@ -23,6 +26,7 @@ import { OnboardingLandingComponent } from './onboarding-landing/onboarding-land
import { OnboardingQboConnectorComponent } from './onboarding-qbo-connector/onboarding-qbo-connector.component';
import { OnboardingEmployeeSettingsComponent } from './onboarding-employee-settings/onboarding-employee-settings.component';
import { OnboardingRoutingModule } from './onboarding-routing.module';
+import { CloneSettingsComponent } from './clone-settings/clone-settings.component';
@NgModule({
declarations: [
@@ -33,7 +37,8 @@ import { OnboardingRoutingModule } from './onboarding-routing.module';
OnboardingComponent,
OnboardingLandingComponent,
OnboardingQboConnectorComponent,
- OnboardingEmployeeSettingsComponent
+ OnboardingEmployeeSettingsComponent,
+ CloneSettingsComponent
],
imports: [
CommonModule,
@@ -46,7 +51,12 @@ import { OnboardingRoutingModule } from './onboarding-routing.module';
MatSlideToggleModule,
MatCheckboxModule,
MatDialogModule,
- SharedModule
+ SharedModule,
+ MatMenuModule,
+ MatTooltipModule,
+ SharedModule,
+ MatProgressSpinnerModule,
+ DragDropModule
]
})
export class OnboardingModule { }
diff --git a/src/app/shared/components/configuration/advanced-settings/advanced-settings.component.spec.ts b/src/app/shared/components/configuration/advanced-settings/advanced-settings.component.spec.ts
index 481c19c1..47140bc7 100644
--- a/src/app/shared/components/configuration/advanced-settings/advanced-settings.component.spec.ts
+++ b/src/app/shared/components/configuration/advanced-settings/advanced-settings.component.spec.ts
@@ -5,7 +5,7 @@ import { UntypedFormBuilder, FormsModule, ReactiveFormsModule, Validators } from
import { MatLegacySnackBar as MatSnackBar, MatLegacySnackBarModule as MatSnackBarModule } from '@angular/material/legacy-snack-bar';
import { HttpClientModule } from '@angular/common/http';
import { SharedModule } from 'src/app/shared/shared.module';
-import { adminEmails, advancedSettingResponse, customFields, destinationAttribute, emailResponse, errorResponse, postExpenseFilterResponse, getadvancedSettingResponse, getadvancedSettingResponse2, getExpenseFilterResponse, memo, previewResponse, response, conditionMock1, conditionMock2, conditionMock3, customOperatorMock1, customOperatorMock2, customOperatorMock3, customOperatorMock4, claimNumberOperators, spentAtOperators, reportTitleOperators, conditionMock4, conditionFieldOptions, getExpenseFilterResponse2, getExpenseFilterResponse3, getExpenseFilterResponse4 } from './advanced-settings.fixture';
+import { adminEmails, advancedSettingResponse, customFields, destinationAttribute, emailResponse, errorResponse, postExpenseFilterResponse, getadvancedSettingResponse, getadvancedSettingResponse2, getExpenseFilterResponse, memo, previewResponse, response, conditionMock1, conditionMock2, conditionMock3, customOperatorMock1, customOperatorMock2, customOperatorMock3, customOperatorMock4, claimNumberOperators, spentAtOperators, reportTitleOperators, conditionMock4, conditionFieldOptions, getExpenseFilterResponse2, getExpenseFilterResponse3, getExpenseFilterResponse4, paymentSyncOptions } from './advanced-settings.fixture';
import { Router } from '@angular/router';
import { AdvancedSettingService } from 'src/app/core/services/configuration/advanced-setting.service';
import { MappingService } from 'src/app/core/services/misc/mapping.service';
@@ -47,7 +47,9 @@ describe('AdvancedSettingsComponent', () => {
getWorkspaceAdmins: () => of(adminEmails),
postExpenseFilter: () => of(postExpenseFilterResponse),
getExpenseFilter: () => of(getExpenseFilterResponse),
- deleteExpenseFilter: () => of()
+ getPaymentSyncOptions: () => paymentSyncOptions,
+ deleteExpenseFilter: () => of(),
+ openAddemailDialog: () => undefined
};
service2 = {
diff --git a/src/app/shared/components/configuration/advanced-settings/advanced-settings.component.ts b/src/app/shared/components/configuration/advanced-settings/advanced-settings.component.ts
index d89a546e..493b0c98 100644
--- a/src/app/shared/components/configuration/advanced-settings/advanced-settings.component.ts
+++ b/src/app/shared/components/configuration/advanced-settings/advanced-settings.component.ts
@@ -50,20 +50,7 @@ export class AdvancedSettingsComponent implements OnInit, OnDestroy {
memoPreviewText: string = '';
- paymentSyncOptions: AdvancedSettingFormOption[] = [
- {
- label: 'None',
- value: null
- },
- {
- label: 'Export Fyle ACH Payments to QuickBooks Online',
- value: PaymentSyncDirection.FYLE_TO_QBO
- },
- {
- label: 'Import QuickBooks Online Payments into Fyle',
- value: PaymentSyncDirection.QBO_TO_FYLE
- }
- ];
+ paymentSyncOptions: AdvancedSettingFormOption[] = this.advancedSettingService.getPaymentSyncOptions();
frequencyIntervals: AdvancedSettingFormOption[] = [...Array(24).keys()].map(day => {
return {
diff --git a/src/app/shared/components/configuration/advanced-settings/advanced-settings.fixture.ts b/src/app/shared/components/configuration/advanced-settings/advanced-settings.fixture.ts
index 680610c7..1ffbf99e 100644
--- a/src/app/shared/components/configuration/advanced-settings/advanced-settings.fixture.ts
+++ b/src/app/shared/components/configuration/advanced-settings/advanced-settings.fixture.ts
@@ -1,8 +1,8 @@
-import { AdvancedSettingGet, AdvancedSettingPost } from "src/app/core/models/configuration/advanced-setting.model";
+import { AdvancedSettingFormOption, AdvancedSettingGet, AdvancedSettingPost } from "src/app/core/models/configuration/advanced-setting.model";
import { DestinationAttribute } from "src/app/core/models/db/destination-attribute.model";
import { WorkspaceSchedule, WorkspaceScheduleEmailOptions } from "src/app/core/models/db/workspace-schedule.model";
import { WorkspaceGeneralSetting } from "src/app/core/models/db/workspace-general-setting.model";
-import { AutoMapEmployee, CorporateCreditCardExpensesObject, CustomOperatorOption, EmployeeFieldMapping, NameInJournalEntry, JoinOption, Operator, ReimbursableExpensesObject } from "src/app/core/models/enum/enum.model";
+import { AutoMapEmployee, CorporateCreditCardExpensesObject, CustomOperatorOption, EmployeeFieldMapping, JoinOption, Operator, PaymentSyncDirection, ReimbursableExpensesObject, NameInJournalEntry } from "src/app/core/models/enum/enum.model";
import { ConditionField, ExpenseFilterResponse, SkipExport } from "src/app/core/models/misc/skip-export.model";
export const response:WorkspaceGeneralSetting = {
@@ -56,6 +56,7 @@ export const advancedSettingResponse:AdvancedSettingGet = {
},
workspace_id: 1
};
+
export const destinationAttribute: DestinationAttribute[] = [{
id: 1,
attribute_type: 'EMPLOYEE',
@@ -107,7 +108,20 @@ export const getadvancedSettingResponse:AdvancedSettingGet = {
},
workspace_id: 1
};
-
+export const paymentSyncOptions = [
+ {
+ label: 'None',
+ value: null
+ },
+ {
+ label: 'Export Fyle ACH Payments to Quickbooks Online',
+ value: PaymentSyncDirection.FYLE_TO_QBO
+ },
+ {
+ label: 'Import Quickbooks Payments into Fyle',
+ value: PaymentSyncDirection.QBO_TO_FYLE
+ }
+];
export const getadvancedSettingResponse2:AdvancedSettingGet = {
workspace_general_settings: {
sync_fyle_to_qbo_payments: true,
diff --git a/src/app/shared/components/configuration/configuration-step-header-section/configuration-step-header-section.component.spec.ts b/src/app/shared/components/configuration/configuration-step-header-section/configuration-step-header-section.component.spec.ts
index ad494f39..8ead9b1b 100644
--- a/src/app/shared/components/configuration/configuration-step-header-section/configuration-step-header-section.component.spec.ts
+++ b/src/app/shared/components/configuration/configuration-step-header-section/configuration-step-header-section.component.spec.ts
@@ -10,6 +10,7 @@ import { HttpClientTestingModule, HttpTestingController } from '@angular/common/
import { environment } from 'src/environments/environment';
import { WorkspaceService } from 'src/app/core/services/workspace/workspace.service';
import { OnboardingState } from 'src/app/core/models/enum/enum.model';
+import { MatLegacyDialogModule as MatDialogModule } from '@angular/material/legacy-dialog';
describe('ConfigurationStepHeaderSectionComponent', () => {
let component: ConfigurationStepHeaderSectionComponent;
@@ -25,7 +26,7 @@ describe('ConfigurationStepHeaderSectionComponent', () => {
dialogRefSpyObj.componentInstance = { body: '' }; // Attach componentInstance to the spy object...
beforeEach(async () => {
await TestBed.configureTestingModule({
- imports: [RouterTestingModule, HttpClientModule, MatSnackBarModule, BrowserAnimationsModule, HttpClientTestingModule],
+ imports: [RouterTestingModule, HttpClientModule, MatSnackBarModule, MatDialogModule, BrowserAnimationsModule, HttpClientTestingModule],
declarations: [ ConfigurationStepHeaderSectionComponent],
providers: [ WorkspaceService, {
provide: Router,
diff --git a/src/app/shared/components/configuration/configuration-toggle-field/configuration-toggle-field.component.scss b/src/app/shared/components/configuration/configuration-toggle-field/configuration-toggle-field.component.scss
index 2fdb78f5..48892f29 100644
--- a/src/app/shared/components/configuration/configuration-toggle-field/configuration-toggle-field.component.scss
+++ b/src/app/shared/components/configuration/configuration-toggle-field/configuration-toggle-field.component.scss
@@ -2,30 +2,6 @@
width: 430px;
}
-:host {
- .mat-slide-toggle {
- &.mat-checked {
- ::ng-deep.mat-slide-toggle-bar::after {
- content: 'Yes';
- font-size: 75%;
- color: #FFFFFF;
- position: absolute;
- left: 8px;
- top: -1px;
- }
- }
- &:not(.mat-checked) {
- ::ng-deep.mat-slide-toggle-bar::after {
- content: 'No';
- font-size: 75%;
- color: #FFFFFF;
- position: absolute;
- left: 25px;
- top: -2px;
- }
- }
- }
-}
.export-schedule-section {
padding-left: 28px;
padding-top: 24px;
diff --git a/src/app/shared/components/configuration/employee-settings/employee-settings.component.spec.ts b/src/app/shared/components/configuration/employee-settings/employee-settings.component.spec.ts
index 90e10f03..7dfd21b6 100644
--- a/src/app/shared/components/configuration/employee-settings/employee-settings.component.spec.ts
+++ b/src/app/shared/components/configuration/employee-settings/employee-settings.component.spec.ts
@@ -46,7 +46,9 @@ describe('EmployeeSettingsComponent', () => {
service1 = {
getEmployeeSettings: () => of(response),
- postEmployeeSettings: () => of(response)
+ postEmployeeSettings: () => of(response),
+ getEmployeeFieldMappingOptions: () => null,
+ getAutoMapEmployeeOptions: () => null
};
service2 = {
diff --git a/src/app/shared/components/configuration/employee-settings/employee-settings.component.ts b/src/app/shared/components/configuration/employee-settings/employee-settings.component.ts
index ea65ebbe..2b6573c9 100644
--- a/src/app/shared/components/configuration/employee-settings/employee-settings.component.ts
+++ b/src/app/shared/components/configuration/employee-settings/employee-settings.component.ts
@@ -6,7 +6,7 @@ import { Router } from '@angular/router';
import { forkJoin } from 'rxjs';
import { EmployeeSettingFormOption, EmployeeSettingGet, EmployeeSettingModel } from 'src/app/core/models/configuration/employee-setting.model';
import { DestinationAttribute } from 'src/app/core/models/db/destination-attribute.model';
-import { AutoMapEmployee, ConfigurationCtaText, EmployeeFieldMapping, OnboardingState, OnboardingStep, ProgressPhase, ReimbursableExpensesObject, UpdateEvent } from 'src/app/core/models/enum/enum.model';
+import { ConfigurationCtaText, EmployeeFieldMapping, OnboardingState, OnboardingStep, ProgressPhase, ReimbursableExpensesObject, UpdateEvent } from 'src/app/core/models/enum/enum.model';
import { ConfirmationDialog } from 'src/app/core/models/misc/confirmation-dialog.model';
import { EmployeeSettingService } from 'src/app/core/services/configuration/employee-setting.service';
import { ExportSettingService } from 'src/app/core/services/configuration/export-setting.service';
@@ -37,35 +37,9 @@ export class EmployeeSettingsComponent implements OnInit, OnDestroy {
existingEmployeeFieldMapping: EmployeeFieldMapping | undefined;
- employeeMappingOptions: EmployeeSettingFormOption[] = [
- {
- value: EmployeeFieldMapping.EMPLOYEE,
- label: 'Employees'
- },
- {
- value: EmployeeFieldMapping.VENDOR,
- label: 'Vendors'
- }
- ];
-
- autoMapEmployeeOptions: EmployeeSettingFormOption[] = [
- {
- value: null,
- label: 'None'
- },
- {
- value: AutoMapEmployee.NAME,
- label: 'Fyle Name to QuickBooks Online Display name'
- },
- {
- value: AutoMapEmployee.EMAIL,
- label: 'Fyle Email to QuickBooks Online Email'
- },
- {
- value: AutoMapEmployee.EMPLOYEE_CODE,
- label: 'Fyle Employee Code to QuickBooks Online Display name'
- }
- ];
+ employeeMappingOptions: EmployeeSettingFormOption[] = this.employeeSettingService.getEmployeeFieldMappingOptions();
+
+ autoMapEmployeeOptions: EmployeeSettingFormOption[] = this.employeeSettingService.getAutoMapEmployeeOptions();
windowReference: Window;
@@ -202,7 +176,7 @@ export class EmployeeSettingsComponent implements OnInit, OnDestroy {
this.setLiveEntityExample(responses[1]);
this.employeeSettingsForm = this.formBuilder.group({
employeeMapping: [this.existingEmployeeFieldMapping, Validators.required],
- autoMapEmployee: [responses[0].workspace_general_settings?.auto_map_employees, Validators.nullValidator]
+ autoMapEmployee: [responses[0].workspace_general_settings?.auto_map_employees]
});
this.reimbursableExportType = responses[2].workspace_general_settings?.reimbursable_expenses_object;
this.isLoading = false;
@@ -218,5 +192,4 @@ export class EmployeeSettingsComponent implements OnInit, OnDestroy {
ngOnInit(): void {
this.setupForm();
}
-
}
diff --git a/src/app/shared/components/configuration/employee-settings/employee-settings.fixture.ts b/src/app/shared/components/configuration/employee-settings/employee-settings.fixture.ts
index 5dd1d3ad..dd1e5453 100644
--- a/src/app/shared/components/configuration/employee-settings/employee-settings.fixture.ts
+++ b/src/app/shared/components/configuration/employee-settings/employee-settings.fixture.ts
@@ -19,7 +19,8 @@ export const response1: ExportSettingGet = {
workspace_general_settings: {
reimbursable_expenses_object: ReimbursableExpensesObject.BILL,
corporate_credit_card_expenses_object: CorporateCreditCardExpensesObject.BILL,
- name_in_journal_entry: NameInJournalEntry.EMPLOYEE
+ name_in_journal_entry: NameInJournalEntry.EMPLOYEE,
+ is_simplify_report_closure_enabled: true
},
general_mappings: {
bank_account: { id: '1', name: 'Fyle' },
diff --git a/src/app/shared/components/configuration/export-settings/export-settings.component.html b/src/app/shared/components/configuration/export-settings/export-settings.component.html
index dab5bd89..97bcb01d 100644
--- a/src/app/shared/components/configuration/export-settings/export-settings.component.html
+++ b/src/app/shared/components/configuration/export-settings/export-settings.component.html
@@ -90,7 +90,7 @@
[isFieldMandatory]="true"
[mandatoryErrorListName]="'expense export grouping'"
[iconPath]="'assets/images/svgs/general/tabs.svg'"
- [label]="generateGroupingLabel('reimbursable')"
+ [label]="generateGroupingLabel(exportSource.REIMBURSABLE)"
[subLabel]="'Grouping reflects how the expense entries of a ' + getExportType(exportSettingsForm.value.reimbursableExportType) + ' are posted in QuickBooks Online. For example, grouping by payment exports all expenses within a Fyle payment queue as one record.'"
[placeholder]="'Select expense export grouping'"
[formControllerName]="'reimbursableExportGroup'"
@@ -232,7 +232,7 @@
[isFieldMandatory]="true"
[mandatoryErrorListName]="'expense export grouping'"
[iconPath]="'assets/images/svgs/general/tabs.svg'"
- [label]="generateGroupingLabel('credit card')"
+ [label]="generateGroupingLabel(exportSource.CREDIT_CARD)"
[subLabel]="'Grouping reflects how the expense entries of a ' + getExportType(exportSettingsForm.value.creditCardExportType) + ' are posted in QuickBooks Online. For example, grouping by payment exports all expenses within a Fyle payment queue as one consolidated record.'"
[placeholder]="'Select expense export grouping'"
[formControllerName]="'creditCardExportGroup'"
diff --git a/src/app/shared/components/configuration/export-settings/export-settings.component.spec.ts b/src/app/shared/components/configuration/export-settings/export-settings.component.spec.ts
index 90277a70..4e5e6666 100644
--- a/src/app/shared/components/configuration/export-settings/export-settings.component.spec.ts
+++ b/src/app/shared/components/configuration/export-settings/export-settings.component.spec.ts
@@ -5,8 +5,8 @@ import { MatLegacySnackBarModule as MatSnackBarModule} from '@angular/material/l
import { ExportSettingsComponent } from './export-settings.component';
import { HttpClientModule } from '@angular/common/http';
import { SharedModule } from 'src/app/shared/shared.module';
-import { CorporateCreditCardExpensesObject, EmployeeFieldMapping, ExpenseGroupingFieldOption, ExpenseState, ExportDateType, OnboardingState, ReimbursableExpensesObject } from 'src/app/core/models/enum/enum.model';
-import { destinationAttribute, errorResponse, exportResponse, exportResponse1, export_settings, replacecontent1, replacecontent2, replacecontent3, workspaceResponse, workspaceResponse1 } from './export-settings.fixture';
+import { CorporateCreditCardExpensesObject, EmployeeFieldMapping, ExpenseGroupingFieldOption, ExpenseState, ExportDateType, ExportSource, OnboardingState, ReimbursableExpensesObject } from 'src/app/core/models/enum/enum.model';
+import { destinationAttribute, errorResponse, exportResponse, exportResponse1, export_settings, mockCCCExpenseStateOptions, mockCreditCardExportType, mockNameInJournalEntry, mockReimbursableExpenseGroupingDateOptions, mockReimbursableExpenseGroupingFieldOptions, mockReimbursableExpenseStateOptions, mockReimbursableExportTypeOptions, replacecontent1, replacecontent2, replacecontent3, workspaceResponse, workspaceResponse1 } from './export-settings.fixture';
import { MappingService } from 'src/app/core/services/misc/mapping.service';
import { WorkspaceService } from 'src/app/core/services/workspace/workspace.service';
import { ExportSettingService } from 'src/app/core/services/configuration/export-setting.service';
@@ -33,7 +33,19 @@ describe('ExportSettingsComponent', () => {
beforeEach(async () => {
service1 = {
getExportSettings: () => of(exportResponse),
- postExportSettings: () => of(exportResponse)
+ postExportSettings: () => of(exportResponse),
+ exportSelectionValidator: () => undefined,
+ createCreditCardExpenseWatcher: () => undefined,
+ createReimbursableExpenseWatcher: () => undefined,
+ getReimbursableExpenseGroupingFieldOptions: () => mockReimbursableExpenseGroupingFieldOptions,
+ getReimbursableExpenseGroupingDateOptions: () => mockReimbursableExpenseGroupingDateOptions,
+ getcreditCardExportTypes: () => undefined,
+ getReimbursableExportTypeOptions: () => undefined,
+ getCCCExpenseStateOptions: () => undefined,
+ getExportGroup: () => undefined,
+ getReimbursableExpenseStateOptions: () => mockReimbursableExpenseStateOptions,
+ setGeneralMappingsValidator: () => undefined,
+ nameInJournalOptions: () => mockNameInJournalEntry
};
service2 = {
getGroupedQBODestinationAttributes: () => of(destinationAttribute),
@@ -69,13 +81,13 @@ describe('ExportSettingsComponent', () => {
component.exportSettings = exportResponse;
component.exportSettingsForm = formbuilder.group({
expenseState: [component.exportSettings.expense_group_settings?.expense_state, Validators.required],
- reimbursableExpense: [component.exportSettings.workspace_general_settings?.reimbursable_expenses_object ? true : false, (component as any).exportSelectionValidator()],
+ reimbursableExpense: [component.exportSettings.workspace_general_settings?.reimbursable_expenses_object ? true : false],
reimbursableExportType: [component.exportSettings.workspace_general_settings?.reimbursable_expenses_object],
- reimbursableExportGroup: [(component as any).getExportGroup(component.exportSettings.expense_group_settings?.reimbursable_expense_group_fields)],
+ reimbursableExportGroup: [exportSettingService.getExportGroup(component.exportSettings.expense_group_settings?.reimbursable_expense_group_fields)],
reimbursableExportDate: [component.exportSettings.expense_group_settings?.reimbursable_export_date_type],
- creditCardExpense: [component.exportSettings.workspace_general_settings?.corporate_credit_card_expenses_object ? true : false, (component as any).exportSelectionValidator()],
+ creditCardExpense: [component.exportSettings.workspace_general_settings?.corporate_credit_card_expenses_object ? true : false],
creditCardExportType: [component.exportSettings.workspace_general_settings?.corporate_credit_card_expenses_object],
- creditCardExportGroup: [(component as any).getExportGroup(component.exportSettings.expense_group_settings?.corporate_credit_card_expense_group_fields)],
+ creditCardExportGroup: [exportSettingService.getExportGroup(component.exportSettings.expense_group_settings?.corporate_credit_card_expense_group_fields)],
creditCardExportDate: [component.exportSettings.expense_group_settings?.ccc_export_date_type],
bankAccount: [component.exportSettings.general_mappings?.bank_account?.id ? component.exportSettings.general_mappings.bank_account : null],
defaultCCCAccount: [component.exportSettings.general_mappings?.default_ccc_account?.id ? component.exportSettings.general_mappings.default_ccc_account : null],
@@ -105,9 +117,6 @@ describe('ExportSettingsComponent', () => {
expect(component.getExportType(ReimbursableExpensesObject.JOURNAL_ENTRY)).toEqual(output);
});
- it('getReimbursableExportTypes function check', () => {
- expect(component.getReimbursableExportTypes(EmployeeFieldMapping.EMPLOYEE)).toEqual(export_settings);
- });
it('navigateToPreviousStep function check', () => {
expect(component.navigateToPreviousStep()).toBeUndefined();
@@ -123,25 +132,9 @@ describe('ExportSettingsComponent', () => {
it('generateGroupingLabel function check', () => {
component.exportSettingsForm.controls.reimbursableExportType.patchValue(ReimbursableExpensesObject.EXPENSE);
fixture.detectChanges();
- expect(component.generateGroupingLabel('reimbursable')).toEqual('How should the expenses be grouped?');
+ expect(component.generateGroupingLabel(ExportSource.REIMBURSABLE)).toEqual('How should the expenses be grouped?');
});
- it('createReimbursableExpenseWatcher function check', () => {
- component.ngOnInit();
- component.exportSettingsForm.controls.reimbursableExpense.patchValue(true);
- expect((component as any).createReimbursableExpenseWatcher()).toBeUndefined();
- fixture.detectChanges();
- component.exportSettingsForm.controls.reimbursableExpense.patchValue(false);
- expect((component as any).createReimbursableExpenseWatcher()).toBeUndefined();
- });
-
- it('createCreditCardExpenseWatcher function check', () => {
- component.exportSettingsForm.controls.creditCardExpense.patchValue(!component.exportSettingsForm.controls.creditCardExpense.value);
- expect((component as any).createCreditCardExpenseWatcher()).toBeUndefined();
- // Fixture.detectChanges();
- component.exportSettingsForm.controls.creditCardExpense.patchValue(!component.exportSettingsForm.controls.creditCardExpense.value);
- expect((component as any).createCreditCardExpenseWatcher()).toBeUndefined();
- });
it('restrictExpenseGroupSetting function check', () => {
expect((component as any).restrictExpenseGroupSetting('CREDIT CARD PURCHASE')).toBeUndefined();
@@ -177,12 +170,6 @@ describe('ExportSettingsComponent', () => {
expect(component.showReimbursableAccountsPayableField()).toBeTrue();
});
- it('setGeneralMappingsValidator function check', () => {
- component.exportSettingsForm.controls.creditCardExportType.patchValue(CorporateCreditCardExpensesObject.DEBIT_CARD_EXPENSE);
- fixture.detectChanges();
- expect((component as any).setGeneralMappingsValidator()).toBeUndefined();
- });
-
it('createReimbursableExportGroupWatcher function check', () => {
const reimbursable = component.exportSettingsForm.controls.reimbursableExportGroup.value;
component.exportSettingsForm.controls.reimbursableExportGroup.patchValue('expense_id');
@@ -201,11 +188,6 @@ describe('ExportSettingsComponent', () => {
expect((component as any).createCreditCardExportGroupWatcher()).toBeUndefined();
});
- it('function check', () => {
- expect((component as any).getExportGroup([ExpenseGroupingFieldOption.EXPENSE_ID])).toEqual('expense_id');
- expect((component as any).getExportGroup(null)).toEqual('');
- });
-
it('advancedSettingAffected function check', () => {
component.exportSettings.workspace_general_settings.corporate_credit_card_expenses_object = CorporateCreditCardExpensesObject.BILL;
component.exportSettings.workspace_general_settings.reimbursable_expenses_object = ReimbursableExpensesObject.CHECK;
@@ -272,15 +254,4 @@ describe('ExportSettingsComponent', () => {
expect(exportSettingService.postExportSettings).toHaveBeenCalled();
expect(component.isLoading).toBeFalse();
});
-
- it('exportSelectionValidator function check', () => {
- const control = { value: ExpenseState.PAID, parent: formbuilder.group({
- reimbursableExpense: ReimbursableExpensesObject.BILL
- }) };
- expect((component as any).exportSelectionValidator()(control as AbstractControl)).toBeNull();
- const control1 = { value: ExpenseState.PAYMENT_PROCESSING, parent: formbuilder.group({
- creditCardExpense: CorporateCreditCardExpensesObject.BILL
- }) };
- expect((component as any).exportSelectionValidator()(control1 as AbstractControl)).toBeNull();
- });
});
diff --git a/src/app/shared/components/configuration/export-settings/export-settings.component.ts b/src/app/shared/components/configuration/export-settings/export-settings.component.ts
index 16d06d5c..da0be457 100644
--- a/src/app/shared/components/configuration/export-settings/export-settings.component.ts
+++ b/src/app/shared/components/configuration/export-settings/export-settings.component.ts
@@ -3,8 +3,8 @@ import { AbstractControl, UntypedFormBuilder, UntypedFormGroup, ValidatorFn, Val
import { Router } from '@angular/router';
import { forkJoin } from 'rxjs';
import { DestinationAttribute } from 'src/app/core/models/db/destination-attribute.model';
-import { ConfigurationCtaText, CorporateCreditCardExpensesObject, EmployeeFieldMapping, ExpenseGroupingFieldOption, ExpenseState, CCCExpenseState, ExportDateType, OnboardingState, OnboardingStep, ProgressPhase, ReimbursableExpensesObject, UpdateEvent, NameInJournalEntry } from 'src/app/core/models/enum/enum.model';
-import { ExportSettingGet, ExportSettingFormOption, ExportSettingModel } from 'src/app/core/models/configuration/export-setting.model';
+import { ConfigurationCtaText, CorporateCreditCardExpensesObject, EmployeeFieldMapping, ExpenseGroupingFieldOption, ExpenseState, CCCExpenseState, ExportDateType, OnboardingState, OnboardingStep, ProgressPhase, ReimbursableExpensesObject, UpdateEvent, NameInJournalEntry, ExportSource } from 'src/app/core/models/enum/enum.model';
+import { ExportSettingGet, ExportSettingFormOption, ExportSettingModel, NameInJournalEntryOptions } from 'src/app/core/models/configuration/export-setting.model';
import { ExportSettingService } from 'src/app/core/services/configuration/export-setting.service';
import { HelperService } from 'src/app/core/services/core/helper.service';
import { MappingService } from 'src/app/core/services/misc/mapping.service';
@@ -55,75 +55,15 @@ export class ExportSettingsComponent implements OnInit, OnDestroy {
cccExpenseStateOptions: ExportSettingFormOption[];
- nameInJournalOptions = [
- {
- label: 'Merchant Name',
- value: NameInJournalEntry.MERCHANT
- },
- {
- label: 'Employee Name',
- value: NameInJournalEntry.EMPLOYEE
- }
- ];
-
- expenseGroupingFieldOptions: ExportSettingFormOption[] = [
- {
- label: 'Report',
- value: ExpenseGroupingFieldOption.CLAIM_NUMBER
- },
- {
- label: 'Payment',
- value: ExpenseGroupingFieldOption.SETTLEMENT_ID
- },
- {
- label: 'Expense',
- value: ExpenseGroupingFieldOption.EXPENSE_ID
- }
- ];
-
- reimbursableExpenseGroupingDateOptions: ExportSettingFormOption[] = [
- {
- 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
- }
- ];
+ nameInJournalOptions: NameInJournalEntryOptions[] = this.exportSettingService.nameInJournalOptions();
+
+ expenseGroupingFieldOptions: ExportSettingFormOption[] = this.exportSettingService.getReimbursableExpenseGroupingFieldOptions();
+
+ reimbursableExpenseGroupingDateOptions: ExportSettingFormOption[] = this.exportSettingService.getReimbursableExpenseGroupingDateOptions();
cccExpenseGroupingDateOptions: ExportSettingFormOption[];
- creditCardExportTypes: ExportSettingFormOption[] = [
- {
- label: 'Bill',
- value: CorporateCreditCardExpensesObject.BILL
- },
- {
- label: 'Credit Card Purchase',
- value: CorporateCreditCardExpensesObject.CREDIT_CARD_PURCHASE
- },
- {
- label: 'Journal Entry',
- value: CorporateCreditCardExpensesObject.JOURNAL_ENTRY
- },
- {
- label: 'Debit Card Expense',
- value: CorporateCreditCardExpensesObject.DEBIT_CARD_EXPENSE
- }
- ];
+ creditCardExportTypes: ExportSettingFormOption[] = this.exportSettingService.getcreditCardExportTypes();
reimbursableExportTypes: ExportSettingFormOption[];
@@ -131,6 +71,8 @@ export class ExportSettingsComponent implements OnInit, OnDestroy {
ProgressPhase = ProgressPhase;
+ exportSource = ExportSource;
+
private readonly sessionStartTime = new Date();
private timeSpentEventRecorded: boolean = false;
@@ -158,9 +100,9 @@ export class ExportSettingsComponent implements OnInit, OnDestroy {
return lowerCaseWord.charAt(0).toUpperCase() + lowerCaseWord.slice(1);
}
- generateGroupingLabel(exportSource: 'reimbursable' | 'credit card'): string {
+ generateGroupingLabel(exportSource: 'credit card' | 'reimbursable'): string {
let exportType: ReimbursableExpensesObject | CorporateCreditCardExpensesObject;
- if (exportSource === 'reimbursable') {
+ if (exportSource === ExportSource.REIMBURSABLE) {
exportType = this.exportSettingsForm.value.reimbursableExportType;
} else {
exportType = this.exportSettingsForm.value.creditCardExportType;
@@ -173,84 +115,6 @@ export class ExportSettingsComponent implements OnInit, OnDestroy {
return `How should the expense in ${this.getExportType(exportType)} be grouped?`;
}
- getReimbursableExportTypes(employeeFieldMapping: EmployeeFieldMapping): ExportSettingFormOption[] {
- return {
- EMPLOYEE: [
- {
- label: 'Check',
- value: ReimbursableExpensesObject.CHECK
- },
- {
- label: 'Expense',
- value: ReimbursableExpensesObject.EXPENSE
- },
- {
- label: 'Journal Entry',
- value: ReimbursableExpensesObject.JOURNAL_ENTRY
- }
- ],
- VENDOR: [
- {
- label: 'Bill',
- value: ReimbursableExpensesObject.BILL
- },
- {
- label: 'Expense',
- value: ReimbursableExpensesObject.EXPENSE
- },
- {
- label: 'Journal Entry',
- value: ReimbursableExpensesObject.JOURNAL_ENTRY
- }
- ]
- }[employeeFieldMapping];
- }
-
- private createReimbursableExpenseWatcher(): void {
- this.exportSettingsForm.controls.reimbursableExpense.valueChanges.subscribe((isReimbursableExpenseSelected) => {
- if (isReimbursableExpenseSelected) {
- this.exportSettingsForm.controls.expenseState.setValidators(Validators.required);
- this.exportSettingsForm.controls.expenseState.setValue(this.exportSettings.expense_group_settings?.expense_state ? this.exportSettings.expense_group_settings?.expense_state : ExpenseState.PAYMENT_PROCESSING);
- this.exportSettingsForm.controls.reimbursableExportType.setValidators(Validators.required);
- this.exportSettingsForm.controls.reimbursableExportGroup.setValidators(Validators.required);
- this.exportSettingsForm.controls.reimbursableExportDate.setValidators(Validators.required);
- } else {
- this.exportSettingsForm.controls.expenseState.clearValidators();
- this.exportSettingsForm.controls.reimbursableExportType.clearValidators();
- this.exportSettingsForm.controls.reimbursableExportGroup.clearValidators();
- this.exportSettingsForm.controls.reimbursableExportDate.clearValidators();
- this.exportSettingsForm.controls.expenseState.setValue(null);
- this.exportSettingsForm.controls.reimbursableExportType.setValue(null);
- this.exportSettingsForm.controls.reimbursableExportGroup.setValue(null);
- this.exportSettingsForm.controls.reimbursableExportDate.setValue(null);
- }
-
- this.setGeneralMappingsValidator();
- });
- }
-
- private createCreditCardExpenseWatcher(): void {
- this.exportSettingsForm.controls.creditCardExpense.valueChanges.subscribe((isCreditCardExpenseSelected) => {
- if (isCreditCardExpenseSelected) {
- this.exportSettingsForm.controls.cccExpenseState.setValidators(Validators.required);
- this.exportSettingsForm.controls.cccExpenseState.setValue(this.exportSettings.expense_group_settings?.ccc_expense_state ? this.exportSettings.expense_group_settings?.ccc_expense_state : this.is_simplify_report_closure_enabled ? CCCExpenseState.APPROVED: CCCExpenseState.PAYMENT_PROCESSING);
- this.exportSettingsForm.controls.creditCardExportType.setValidators(Validators.required);
- this.exportSettingsForm.controls.creditCardExportGroup.setValidators(Validators.required);
- this.exportSettingsForm.controls.creditCardExportDate.setValidators(Validators.required);
- } else {
- this.exportSettingsForm.controls.cccExpenseState.clearValidators();
- this.exportSettingsForm.controls.creditCardExportType.clearValidators();
- this.exportSettingsForm.controls.creditCardExportGroup.clearValidators();
- this.exportSettingsForm.controls.creditCardExportDate.clearValidators();
- this.exportSettingsForm.controls.cccExpenseState.setValue(null);
- this.exportSettingsForm.controls.creditCardExportType.setValue(null);
- this.exportSettingsForm.controls.creditCardExportGroup.setValue(null);
- this.exportSettingsForm.controls.creditCardExportDate.setValue(null);
- }
-
- this.setGeneralMappingsValidator();
- });
- }
private restrictExpenseGroupSetting(creditCardExportType: string | null) : void {
if (creditCardExportType === CorporateCreditCardExpensesObject.CREDIT_CARD_PURCHASE || creditCardExportType === CorporateCreditCardExpensesObject.DEBIT_CARD_EXPENSE) {
@@ -284,53 +148,19 @@ export class ExportSettingsComponent implements OnInit, OnDestroy {
private createReimbursableExportTypeWatcher(): void {
this.exportSettingsForm.controls.reimbursableExportType.valueChanges.subscribe(() => {
- this.setGeneralMappingsValidator();
- });
+ this.exportSettingService.setGeneralMappingsValidator(this.exportSettingsForm);
+});
}
private createCreditCardExportTypeWatcher(): void {
this.restrictExpenseGroupSetting(this.exportSettings.workspace_general_settings.corporate_credit_card_expenses_object);
this.exportSettingsForm.controls.creditCardExportType.valueChanges.subscribe((creditCardExportType: string) => {
- this.setGeneralMappingsValidator();
+ this.exportSettingService.setGeneralMappingsValidator(this.exportSettingsForm);
this.restrictExpenseGroupSetting(creditCardExportType);
this.showNameInJournalOption = creditCardExportType === CorporateCreditCardExpensesObject.JOURNAL_ENTRY ? true : false;
});
}
- private exportSelectionValidator(): ValidatorFn {
- return (control: AbstractControl): {[key: string]: object} | null => {
- let forbidden = true;
- if (this.exportSettingsForm) {
- if (typeof control.value === 'boolean') {
- if (control.value) {
- forbidden = false;
- } else {
- if (control.parent?.get('reimbursableExpense')?.value || control.parent?.get('creditCardExpense')?.value) {
- forbidden = false;
- }
- }
- } else if ((control.value === ExpenseState.PAID || control.value === ExpenseState.PAYMENT_PROCESSING || control.value === CCCExpenseState.APPROVED)
- && (control.parent?.get('reimbursableExpense')?.value || control.parent?.get('creditCardExpense')?.value)) {
- forbidden = false;
- }
-
- if (!forbidden) {
- control.parent?.get('expenseState')?.setErrors(null);
- control.parent?.get('cccExpenseState')?.setErrors(null);
- control.parent?.get('reimbursableExpense')?.setErrors(null);
- control.parent?.get('creditCardExpense')?.setErrors(null);
- return null;
- }
- }
-
- return {
- forbiddenOption: {
- value: control.value
- }
- };
- };
- }
-
showBankAccountField(): boolean {
return this.employeeFieldMapping === EmployeeFieldMapping.EMPLOYEE && this.exportSettingsForm.controls.reimbursableExportType.value && this.exportSettingsForm.controls.reimbursableExportType.value !== ReimbursableExpensesObject.EXPENSE;
}
@@ -363,50 +193,6 @@ export class ExportSettingsComponent implements OnInit, OnDestroy {
return (this.exportSettingsForm.controls.reimbursableExportType.value === ReimbursableExpensesObject.BILL || this.exportSettingsForm.controls.creditCardExportType.value === CorporateCreditCardExpensesObject.BILL) ? ReimbursableExpensesObject.BILL : ReimbursableExpensesObject.JOURNAL_ENTRY;
}
- private setGeneralMappingsValidator(): void {
- if (this.showBankAccountField()) {
- this.exportSettingsForm.controls.bankAccount.setValidators(Validators.required);
- } else {
- this.exportSettingsForm.controls.bankAccount.clearValidators();
- this.exportSettingsForm.controls.bankAccount.updateValueAndValidity();
- }
-
- if (this.showCreditCardAccountField()) {
- this.exportSettingsForm.controls.defaultCCCAccount.setValidators(Validators.required);
- } else {
- this.exportSettingsForm.controls.defaultCCCAccount.clearValidators();
- this.exportSettingsForm.controls.defaultCCCAccount.updateValueAndValidity();
- }
-
- if (this.showDebitCardAccountField()) {
- this.exportSettingsForm.controls.defaultDebitCardAccount.setValidators(Validators.required);
- } else {
- this.exportSettingsForm.controls.defaultDebitCardAccount.clearValidators();
- this.exportSettingsForm.controls.defaultDebitCardAccount.updateValueAndValidity();
- }
-
- if (this.showReimbursableAccountsPayableField() || this.showCCCAccountsPayableField()) {
- this.exportSettingsForm.controls.accountsPayable.setValidators(Validators.required);
- } else {
- this.exportSettingsForm.controls.accountsPayable.clearValidators();
- this.exportSettingsForm.controls.accountsPayable.updateValueAndValidity();
- }
-
- if (this.showDefaultCreditCardVendorField()) {
- this.exportSettingsForm.controls.defaultCreditCardVendor.setValidators(Validators.required);
- } else {
- this.exportSettingsForm.controls.defaultCreditCardVendor.clearValidators();
- this.exportSettingsForm.controls.defaultCreditCardVendor.updateValueAndValidity();
- }
-
- if (this.showExpenseAccountField()) {
- this.exportSettingsForm.controls.qboExpenseAccount.setValidators(Validators.required);
- } else {
- this.exportSettingsForm.controls.qboExpenseAccount.clearValidators();
- this.exportSettingsForm.controls.qboExpenseAccount.updateValueAndValidity();
- }
- }
-
private createReimbursableExportGroupWatcher(): void {
this.exportSettingsForm.controls.reimbursableExportGroup.valueChanges.subscribe((reimbursableExportGroup: ExpenseGroupingFieldOption) => {
if (reimbursableExportGroup === ExpenseGroupingFieldOption.EXPENSE_ID) {
@@ -444,14 +230,21 @@ export class ExportSettingsComponent implements OnInit, OnDestroy {
});
}
+ private setupExportWatchers(): void {
+ this.exportSettingsForm?.controls.reimbursableExpense?.setValidators(this.exportSettingService.exportSelectionValidator(this.exportSettingsForm));
+ this.exportSettingsForm?.controls.creditCardExpense?.setValidators(this.exportSettingService.exportSelectionValidator(this.exportSettingsForm));
+ }
+
private setCustomValidatorsAndWatchers(): void {
+ this.setupExportWatchers();
+
// Date grouping
this.setCreditCardExpenseGroupingDateOptions(this.exportSettingsForm.controls.creditCardExportGroup.value);
// Toggles
- this.createReimbursableExpenseWatcher();
- this.createCreditCardExpenseWatcher();
+ this.exportSettingService.createReimbursableExpenseWatcher(this.exportSettingsForm, this.exportSettings);
+ this.exportSettingService.createCreditCardExpenseWatcher(this.exportSettingsForm, this.exportSettings);
// Export select fields
this.createReimbursableExportTypeWatcher();
@@ -461,18 +254,7 @@ export class ExportSettingsComponent implements OnInit, OnDestroy {
this.createReimbursableExportGroupWatcher();
this.createCreditCardExportGroupWatcher();
- this.setGeneralMappingsValidator();
- }
-
- private getExportGroup(exportGroups: string[] | null): string {
- if (exportGroups) {
- const exportGroup = exportGroups.find((exportGroup) => {
- return exportGroup === ExpenseGroupingFieldOption.EXPENSE_ID || exportGroup === ExpenseGroupingFieldOption.CLAIM_NUMBER || exportGroup === ExpenseGroupingFieldOption.SETTLEMENT_ID;
- });
- return exportGroup ? exportGroup : ExpenseGroupingFieldOption.CLAIM_NUMBER;
- }
-
- return '';
+ this.exportSettingService.setGeneralMappingsValidator(this.exportSettingsForm);
}
private getSettingsAndSetupForm(): void {
@@ -485,7 +267,6 @@ export class ExportSettingsComponent implements OnInit, OnDestroy {
]).subscribe(response => {
this.exportSettings = response[0];
this.employeeFieldMapping = response[2].employee_field_mapping;
- this.reimbursableExportTypes = this.getReimbursableExportTypes(this.employeeFieldMapping);
this.bankAccounts = response[1].BANK_ACCOUNT;
this.cccAccounts = response[1].CREDIT_CARD_ACCOUNT;
@@ -495,28 +276,9 @@ export class ExportSettingsComponent implements OnInit, OnDestroy {
this.is_simplify_report_closure_enabled = response[2].is_simplify_report_closure_enabled;
this.import_items = response[2].import_items;
- this.cccExpenseStateOptions = [
- {
- label: this.is_simplify_report_closure_enabled ? 'Approved' : 'Payment Processing',
- value: this.is_simplify_report_closure_enabled ? CCCExpenseState.APPROVED: CCCExpenseState.PAYMENT_PROCESSING
- },
- {
- label: this.is_simplify_report_closure_enabled ? 'Closed' : 'Paid',
- value: CCCExpenseState.PAID
- }
- ];
-
- this.expenseStateOptions = [
- {
- label: this.is_simplify_report_closure_enabled ? 'Processing' : 'Payment Processing',
- value: ExpenseState.PAYMENT_PROCESSING
- },
- {
- label: this.is_simplify_report_closure_enabled ? 'Closed' : 'Paid',
- value: ExpenseState.PAID
- }
- ];
-
+ this.reimbursableExportTypes = this.exportSettingService.getReimbursableExportTypeOptions(this.employeeFieldMapping);
+ this.cccExpenseStateOptions = this.exportSettingService.getCCCExpenseStateOptions(this.is_simplify_report_closure_enabled);
+ this.expenseStateOptions = this.exportSettingService.getReimbursableExpenseStateOptions(this.is_simplify_report_closure_enabled);
this.showNameInJournalOption = this.exportSettings.workspace_general_settings?.corporate_credit_card_expenses_object === CorporateCreditCardExpensesObject.JOURNAL_ENTRY ? true : false;
this.setupForm();
@@ -526,14 +288,14 @@ export class ExportSettingsComponent implements OnInit, OnDestroy {
private setupForm(): void {
this.exportSettingsForm = this.formBuilder.group({
expenseState: [this.exportSettings.expense_group_settings?.expense_state],
- reimbursableExpense: [this.exportSettings.workspace_general_settings?.reimbursable_expenses_object ? true : false, this.exportSelectionValidator()],
+ reimbursableExpense: [this.exportSettings.workspace_general_settings?.reimbursable_expenses_object ? true : false],
reimbursableExportType: [this.exportSettings.workspace_general_settings?.reimbursable_expenses_object],
- reimbursableExportGroup: [this.getExportGroup(this.exportSettings.expense_group_settings?.reimbursable_expense_group_fields)],
+ reimbursableExportGroup: [this.exportSettingService.getExportGroup(this.exportSettings.expense_group_settings?.reimbursable_expense_group_fields)],
reimbursableExportDate: [this.exportSettings.expense_group_settings?.reimbursable_export_date_type],
cccExpenseState: [this.exportSettings.expense_group_settings?.ccc_expense_state],
- creditCardExpense: [this.exportSettings.workspace_general_settings?.corporate_credit_card_expenses_object ? true : false, this.exportSelectionValidator()],
+ creditCardExpense: [this.exportSettings.workspace_general_settings?.corporate_credit_card_expenses_object ? true : false],
creditCardExportType: [this.exportSettings.workspace_general_settings?.corporate_credit_card_expenses_object],
- creditCardExportGroup: [this.getExportGroup(this.exportSettings.expense_group_settings?.corporate_credit_card_expense_group_fields)],
+ creditCardExportGroup: [this.exportSettingService.getExportGroup(this.exportSettings.expense_group_settings?.corporate_credit_card_expense_group_fields)],
creditCardExportDate: [this.exportSettings.expense_group_settings?.ccc_export_date_type],
bankAccount: [this.exportSettings.general_mappings?.bank_account?.id ? this.exportSettings.general_mappings.bank_account : null],
defaultCCCAccount: [this.exportSettings.general_mappings?.default_ccc_account?.id ? this.exportSettings.general_mappings.default_ccc_account : null],
diff --git a/src/app/shared/components/configuration/export-settings/export-settings.fixture.ts b/src/app/shared/components/configuration/export-settings/export-settings.fixture.ts
index c69c5426..42f8fdf6 100644
--- a/src/app/shared/components/configuration/export-settings/export-settings.fixture.ts
+++ b/src/app/shared/components/configuration/export-settings/export-settings.fixture.ts
@@ -1,7 +1,7 @@
import { ExportSettingFormOption, ExportSettingGet } from "src/app/core/models/configuration/export-setting.model";
import { GroupedDestinationAttribute } from "src/app/core/models/db/destination-attribute.model";
import { WorkspaceGeneralSetting } from "src/app/core/models/db/workspace-general-setting.model";
-import { AutoMapEmployee, CorporateCreditCardExpensesObject, EmployeeFieldMapping, ExpenseState, CCCExpenseState, ExportDateType, ReimbursableExpensesObject, NameInJournalEntry } from "src/app/core/models/enum/enum.model";
+import { AutoMapEmployee, CorporateCreditCardExpensesObject, EmployeeFieldMapping, ExpenseState, CCCExpenseState, ExportDateType, ReimbursableExpensesObject, ExpenseGroupingFieldOption, NameInJournalEntry } from "src/app/core/models/enum/enum.model";
export const export_settings: ExportSettingFormOption[] = [
{
@@ -152,7 +152,8 @@ export const exportResponse1: ExportSettingGet = {
workspace_general_settings: {
reimbursable_expenses_object: ReimbursableExpensesObject.BILL,
corporate_credit_card_expenses_object: null,
- name_in_journal_entry: NameInJournalEntry.EMPLOYEE
+ name_in_journal_entry: NameInJournalEntry.EMPLOYEE,
+ is_simplify_report_closure_enabled: true
},
workspace_id: 1,
general_mappings: {
@@ -186,7 +187,7 @@ export const exportResponse: ExportSettingGet = {
expense_group_settings: {
expense_state: ExpenseState.PAID,
ccc_expense_state: CCCExpenseState.PAID,
- reimbursable_expense_group_fields: ['sample'],
+ reimbursable_expense_group_fields: [ExpenseGroupingFieldOption.EXPENSE_ID],
reimbursable_export_date_type: ExportDateType.APPROVED_AT,
corporate_credit_card_expense_group_fields: ['sipper'],
ccc_export_date_type: ExportDateType.SPENT_AT
@@ -194,7 +195,8 @@ export const exportResponse: ExportSettingGet = {
workspace_general_settings: {
reimbursable_expenses_object: ReimbursableExpensesObject.BILL,
corporate_credit_card_expenses_object: CorporateCreditCardExpensesObject.BILL,
- name_in_journal_entry: NameInJournalEntry.EMPLOYEE
+ name_in_journal_entry: NameInJournalEntry.EMPLOYEE,
+ is_simplify_report_closure_enabled: true
},
general_mappings: {
bank_account: { id: '1', name: 'Fyle' },
@@ -227,3 +229,125 @@ export const errorResponse = {
company_name: 'QBO'
}
};
+
+export const mockReimbursableExpenseGroupingFieldOptions = [
+ {
+ label: 'Report',
+ value: ExpenseGroupingFieldOption.CLAIM_NUMBER
+ },
+ {
+ label: 'Payment',
+ value: ExpenseGroupingFieldOption.SETTLEMENT_ID
+ },
+ {
+ label: 'Expense',
+ value: ExpenseGroupingFieldOption.EXPENSE_ID
+ }
+];
+
+export const mockReimbursableExpenseGroupingDateOptions = [
+
+ {
+ 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
+ }
+];
+
+export const mockCreditCardExportType = [
+ {
+ label: 'Bill',
+ value: CorporateCreditCardExpensesObject.BILL
+ },
+ {
+ label: 'Credit Card Purchase',
+ value: CorporateCreditCardExpensesObject.CREDIT_CARD_PURCHASE
+ },
+ {
+ label: 'Journal Entry',
+ value: CorporateCreditCardExpensesObject.JOURNAL_ENTRY
+ },
+ {
+ label: 'Debit Card Expense',
+ value: CorporateCreditCardExpensesObject.DEBIT_CARD_EXPENSE
+ }
+];
+
+export const mockReimbursableExportTypeOptions = {
+ EMPLOYEE: [
+ {
+ label: 'Check',
+ value: ReimbursableExpensesObject.CHECK
+ },
+ {
+ label: 'Expense',
+ value: ReimbursableExpensesObject.EXPENSE
+ },
+ {
+ label: 'Journal Entry',
+ value: ReimbursableExpensesObject.JOURNAL_ENTRY
+ }
+ ],
+ VENDOR: [
+ {
+ label: 'Bill',
+ value: ReimbursableExpensesObject.BILL
+ },
+ {
+ label: 'Expense',
+ value: ReimbursableExpensesObject.EXPENSE
+ },
+ {
+ label: 'Journal Entry',
+ value: ReimbursableExpensesObject.JOURNAL_ENTRY
+ }
+ ]
+};
+
+export const mockCCCExpenseStateOptions = [
+ {
+ label: 'Payment Processing',
+ value: CCCExpenseState.PAYMENT_PROCESSING
+ },
+ {
+ label: 'Paid',
+ value: CCCExpenseState.PAID
+ }
+];
+
+export const mockReimbursableExpenseStateOptions = [
+ {
+ label: 'Processing',
+ value: ExpenseState.PAYMENT_PROCESSING
+ },
+ {
+ label: 'Closed',
+ value: ExpenseState.PAID
+ }
+];
+
+export const mockNameInJournalEntry = [
+ {
+ label: 'Merchant Name',
+ value: NameInJournalEntry.MERCHANT
+ },
+ {
+ label: 'Employee Name',
+ value: NameInJournalEntry.EMPLOYEE
+ }
+];
diff --git a/src/app/shared/components/configuration/import-settings/expense-field-creation-dialog/expense-field-creation-dialog.component.ts b/src/app/shared/components/configuration/import-settings/expense-field-creation-dialog/expense-field-creation-dialog.component.ts
index a98e1714..4c686404 100644
--- a/src/app/shared/components/configuration/import-settings/expense-field-creation-dialog/expense-field-creation-dialog.component.ts
+++ b/src/app/shared/components/configuration/import-settings/expense-field-creation-dialog/expense-field-creation-dialog.component.ts
@@ -39,6 +39,7 @@ export class ExpenseFieldCreationDialogComponent implements OnInit {
name: this.expenseFieldsCreationForm.get('name')?.value,
source_placeholder: this.expenseFieldsCreationForm.get('placeholder')?.value
};
+
this.dialogRef.close(expenseField);
}
diff --git a/src/app/shared/components/configuration/import-settings/import-settings.component.spec.ts b/src/app/shared/components/configuration/import-settings/import-settings.component.spec.ts
index 0eb468a5..10fc6809 100644
--- a/src/app/shared/components/configuration/import-settings/import-settings.component.spec.ts
+++ b/src/app/shared/components/configuration/import-settings/import-settings.component.spec.ts
@@ -8,7 +8,7 @@ import { HttpClientModule } from '@angular/common/http';
import { SharedModule } from 'src/app/shared/shared.module';
import { Router } from '@angular/router';
import { of, throwError } from 'rxjs';
-import { chartOfAccountTypesList, errorResponse, destinationAttribute, expenseFieldresponse, getImportsettingResponse, postImportsettingresponse, QBOCredentialsResponse, qboField } from './import-settings.fixture';
+import { chartOfAccountTypesList, errorResponse, destinationAttribute, expenseFieldresponse, getImportsettingResponse, postImportsettingresponse, QBOCredentialsResponse, qboField, mockExpenseFieldsFormArray, mockPatchExpenseFieldsFormArray } from './import-settings.fixture';
import { MappingDestinationField, OnboardingState } from 'src/app/core/models/enum/enum.model';
import { ImportSettingService } from 'src/app/core/services/configuration/import-setting.service';
import { WorkspaceService } from 'src/app/core/services/workspace/workspace.service';
@@ -41,7 +41,9 @@ describe('ImportSettingsComponent', () => {
beforeEach(async () => {
service1 = {
getImportSettings: () => of(getImportsettingResponse),
- postImportSettings: () => of(postImportsettingresponse)
+ postImportSettings: () => of(postImportsettingresponse),
+ getExpenseFieldsFormArray: () => mockExpenseFieldsFormArray,
+ patchExpenseFieldEmitter: of(mockPatchExpenseFieldsFormArray)
};
service2 = {
getFyleExpenseFields: () => of(expenseFieldresponse),
diff --git a/src/app/shared/components/configuration/import-settings/import-settings.component.ts b/src/app/shared/components/configuration/import-settings/import-settings.component.ts
index 9ad70db9..36420e4e 100644
--- a/src/app/shared/components/configuration/import-settings/import-settings.component.ts
+++ b/src/app/shared/components/configuration/import-settings/import-settings.component.ts
@@ -120,42 +120,28 @@ export class ImportSettingsComponent implements OnInit, OnDestroy {
});
}
+ private setupExpenseFieldWatcher(): void {
+ this.importSettingService.patchExpenseFieldEmitter.subscribe((expenseField) => {
+ if (expenseField.addSourceField) {
+ this.fyleExpenseFields.push(expenseField.source_field);
+ }
+ this.expenseFields.controls.filter(field => field.value.destination_field === expenseField.destination_field)[0].patchValue(expenseField);
+ });
+ }
+
private setCustomValidatorsAndWatchers(): void {
+ this.setupExpenseFieldWatcher();
this.updateTaxGroupVisibility();
this.createTaxCodeWatcher();
}
- private importToggleWatcher(): ValidatorFn {
- return (control: AbstractControl): {[key: string]: object} | null => {
- if (control.value) {
- // Mark Fyle field as mandatory if toggle is enabled
- control.parent?.get('source_field')?.setValidators(Validators.required);
- control.parent?.get('source_field')?.setValidators(RxwebValidators.unique());
- } else {
- // Reset Fyle field if toggle is disabled
- control.parent?.get('source_field')?.clearValidators();
- control.parent?.get('source_field')?.setValue(null);
- }
-
- return null;
- };
- }
-
showImportVendors(): boolean {
return !this.autoCreateMerchantsAsVendors;
}
private setupForm(): void {
const chartOfAccountTypeFormArray = this.chartOfAccountTypesList.map((type) => this.createChartOfAccountField(type));
- const expenseFieldsFormArray = this.qboExpenseFields.map((field) => {
- return this.formBuilder.group({
- source_field: [field.source_field],
- destination_field: [field.destination_field],
- import_to_fyle: [field.import_to_fyle, this.importToggleWatcher()],
- disable_import_to_fyle: [field.disable_import_to_fyle],
- source_placeholder: ['']
- });
- });
+ const expenseFieldsFormArray = this.importSettingService.getExpenseFieldsFormArray(this.qboExpenseFields, true);
this.importSettingsForm = this.formBuilder.group({
chartOfAccount: [this.importSettings.workspace_general_settings.import_categories],
diff --git a/src/app/shared/components/configuration/import-settings/import-settings.fixture.ts b/src/app/shared/components/configuration/import-settings/import-settings.fixture.ts
index fd7a5eac..e63d1894 100644
--- a/src/app/shared/components/configuration/import-settings/import-settings.fixture.ts
+++ b/src/app/shared/components/configuration/import-settings/import-settings.fixture.ts
@@ -6,6 +6,7 @@ import { ExpenseField } from "src/app/core/models/misc/expense-field.model";
import { MappingSetting } from "src/app/core/models/db/mapping-setting.model";
import { DestinationAttribute } from "src/app/core/models/db/destination-attribute.model";
import { QBOCredentials } from "src/app/core/models/configuration/qbo-connector.model";
+import { FormBuilder, FormGroup } from "@angular/forms";
const workspaceresponse:WorkspaceGeneralSetting = {
auto_create_destination_entity: true,
@@ -184,3 +185,28 @@ export const errorResponse = {
company_name: 'QBO'
}
};
+export const mockExpenseFieldsFormArray: FormGroup[] = [
+ new FormBuilder().group({
+ source_field: ['PROJECT'],
+ destination_field: ['CUSTOMER'],
+ disable_import_to_fyle: [false],
+ import_to_fyle: [true],
+ source_placeholder: ['']
+ })
+];
+
+export const mockAdditionalEmailOptions: FormGroup[] = [
+ new FormBuilder().group({
+ name: ['NILESH'],
+ email: ['nilesh.p@fyle.in']
+ })
+];
+
+export const mockPatchExpenseFieldsFormArray = {
+ source_field: 'PROJECT',
+ destination_field: 'CUSTOMER',
+ import_to_fyle: true,
+ disable_import_to_fyle: false,
+ source_placeholder: '',
+ addSourceField: true
+};
diff --git a/src/app/shared/components/configuration/qbo-connector/qbo-connector.component.spec.ts b/src/app/shared/components/configuration/qbo-connector/qbo-connector.component.spec.ts
index 73a78887..e8a411a3 100644
--- a/src/app/shared/components/configuration/qbo-connector/qbo-connector.component.spec.ts
+++ b/src/app/shared/components/configuration/qbo-connector/qbo-connector.component.spec.ts
@@ -13,6 +13,7 @@ import { WorkspaceService } from 'src/app/core/services/workspace/workspace.serv
import { ConfirmationDialog } from 'src/app/core/models/misc/confirmation-dialog.model';
import { MatLegacyDialog as MatDialog, MAT_LEGACY_DIALOG_DATA as MAT_DIALOG_DATA } from '@angular/material/legacy-dialog';
import { AuthService } from 'src/app/core/services/core/auth.service';
+import { environment } from 'src/environments/environment';
describe('QboConnectorComponent', () => {
let component: QboConnectorComponent;
@@ -49,7 +50,8 @@ describe('QboConnectorComponent', () => {
};
service3 = {
refreshQBODimensions: () => of({}),
- setOnboardingState: () => undefined
+ setOnboardingState: () => undefined,
+ getWorkspaceId: () => environment.tests.workspaceId
};
service4 = {
logout: () => undefined,
@@ -126,7 +128,6 @@ describe('QboConnectorComponent', () => {
component.isContinueDisabled = false;
fixture.detectChanges();
expect(component.continueToNextStep()).toBeUndefined();
- expect(router.navigate).toHaveBeenCalledWith([`/workspaces/onboarding/employee_settings`]);
});
it('continueToNextStep => isContinueDisabled = true function check', () => {
diff --git a/src/app/shared/components/configuration/qbo-connector/qbo-connector.component.ts b/src/app/shared/components/configuration/qbo-connector/qbo-connector.component.ts
index a7aa8e18..712a4896 100644
--- a/src/app/shared/components/configuration/qbo-connector/qbo-connector.component.ts
+++ b/src/app/shared/components/configuration/qbo-connector/qbo-connector.component.ts
@@ -15,6 +15,9 @@ import { ConfirmationDialog } from 'src/app/core/models/misc/confirmation-dialog
import { MatLegacyDialog as MatDialog } from '@angular/material/legacy-dialog';
import { ConfirmationDialogComponent } from '../../core/confirmation-dialog/confirmation-dialog.component';
import { TrackingService } from 'src/app/core/services/integration/tracking.service';
+import { HelperService } from 'src/app/core/services/core/helper.service';
+import { CloneSettingService } from 'src/app/core/services/configuration/clone-setting.service';
+import { CloneSettingExist } from 'src/app/core/models/configuration/clone-setting.model';
@Component({
selector: 'app-qbo-connector',
@@ -49,6 +52,9 @@ export class QboConnectorComponent implements OnInit, OnDestroy {
private timeSpentEventRecorded: boolean = false;
+ private disableCloneSettings: boolean;
+
+
constructor(
private authService: AuthService,
private dialog: MatDialog,
@@ -60,7 +66,9 @@ export class QboConnectorComponent implements OnInit, OnDestroy {
private trackingService: TrackingService,
private userService: UserService,
private windowService: WindowService,
- private workspaceService: WorkspaceService
+ private workspaceService: WorkspaceService,
+ private cloneSettingService: CloneSettingService,
+ private helperService: HelperService
) {
this.windowReference = this.windowService.nativeWindow;
}
@@ -72,13 +80,26 @@ export class QboConnectorComponent implements OnInit, OnDestroy {
this.trackingService.trackTimeSpent(OnboardingStep.CONNECT_QBO, {phase: ProgressPhase.ONBOARDING, durationInSeconds: Math.floor(differenceInMs / 1000), eventState: eventState});
}
+ checkCloneSettingsAvailablity(): void {
+ this.cloneSettingService.checkCloneSettingsExists().subscribe((response: CloneSettingExist) => {
+ if (response.is_available) {
+ this.showCloneSettingsDialog(response.workspace_name);
+ } else {
+ this.router.navigate(['/workspaces/onboarding/employee_settings']);
+ }
+ });
+ }
+
continueToNextStep(): void {
if (this.isContinueDisabled) {
return;
+ } else if (this.disableCloneSettings) {
+ this.router.navigate(['/workspaces/onboarding/employee_settings']);
+ return;
}
this.trackSessionTime('success');
- this.router.navigate([`/workspaces/onboarding/employee_settings`]);
+ this.checkCloneSettingsAvailablity();
}
switchFyleOrg(): void {
@@ -116,6 +137,21 @@ export class QboConnectorComponent implements OnInit, OnDestroy {
});
}
+ private showCloneSettingsDialog(workspaceName: string): void {
+ this.isContinueDisabled = false;
+ this.disableCloneSettings = true;
+ const data: ConfirmationDialog = {
+ title: 'Your settings are pre-filled',
+ contents: `Your previous organization's settings (${workspaceName}) have been copied over to the current organization
+
You can change the settings or reset the configuration to restart the process from the beginning
`,
+ primaryCtaText: 'Continue',
+ hideSecondaryCTA: true,
+ hideWarningIcon: true
+ };
+
+ this.helperService.openDialogAndSetupRedirection(data, '/workspaces/onboarding/clone_settings');
+ }
+
private showWarningDialog(): void {
const data: ConfirmationDialog = {
title: 'Incorrect account selected',
diff --git a/src/app/shared/components/configuration/qbo-connector/qbo-connector.fixture.ts b/src/app/shared/components/configuration/qbo-connector/qbo-connector.fixture.ts
index 49a8a415..eb7a1e23 100644
--- a/src/app/shared/components/configuration/qbo-connector/qbo-connector.fixture.ts
+++ b/src/app/shared/components/configuration/qbo-connector/qbo-connector.fixture.ts
@@ -46,7 +46,8 @@ export const exportResponse: ExportSettingGet = {
workspace_general_settings: {
reimbursable_expenses_object: null,
corporate_credit_card_expenses_object: null,
- name_in_journal_entry: NameInJournalEntry.EMPLOYEE
+ name_in_journal_entry: NameInJournalEntry.EMPLOYEE,
+ is_simplify_report_closure_enabled: true
},
general_mappings: {
bank_account: { id: '1', name: 'Fyle' },
diff --git a/src/app/shared/components/core/confirmation-dialog/confirmation-dialog.component.html b/src/app/shared/components/core/confirmation-dialog/confirmation-dialog.component.html
index 67fbd414..8f5e9fea 100644
--- a/src/app/shared/components/core/confirmation-dialog/confirmation-dialog.component.html
+++ b/src/app/shared/components/core/confirmation-dialog/confirmation-dialog.component.html
@@ -2,11 +2,11 @@
diff --git a/src/app/shared/components/helpers/onboarding-stepper/onboarding-stepper.component.scss b/src/app/shared/components/helpers/onboarding-stepper/onboarding-stepper.component.scss
index b593a83d..99c0be4b 100644
--- a/src/app/shared/components/helpers/onboarding-stepper/onboarding-stepper.component.scss
+++ b/src/app/shared/components/helpers/onboarding-stepper/onboarding-stepper.component.scss
@@ -58,6 +58,10 @@
margin-left: -24px;
}
+ &--step-6-text {
+ margin-left: -10px;
+ }
+
&--icon {
width: 24px;
height: 24px;
diff --git a/src/app/shared/components/helpers/onboarding-stepper/onboarding-stepper.component.spec.ts b/src/app/shared/components/helpers/onboarding-stepper/onboarding-stepper.component.spec.ts
index db6dc9bd..97bdffda 100644
--- a/src/app/shared/components/helpers/onboarding-stepper/onboarding-stepper.component.spec.ts
+++ b/src/app/shared/components/helpers/onboarding-stepper/onboarding-stepper.component.spec.ts
@@ -49,4 +49,9 @@ describe('OnboardingStepperComponent', () => {
component.navigate(true, '/login');
expect(component.navigate).toBeTruthy();
});
+
+ it('updateActiveAndCompletedSteps', () => {
+ component.currentStep = 'Clone Settings';
+ expect((component as any).updateActiveAndCompletedSteps());
+ });
});
diff --git a/src/app/shared/components/helpers/onboarding-stepper/onboarding-stepper.component.ts b/src/app/shared/components/helpers/onboarding-stepper/onboarding-stepper.component.ts
index 46ec33e2..9dc282a0 100644
--- a/src/app/shared/components/helpers/onboarding-stepper/onboarding-stepper.component.ts
+++ b/src/app/shared/components/helpers/onboarding-stepper/onboarding-stepper.component.ts
@@ -14,6 +14,8 @@ export class OnboardingStepperComponent implements OnInit {
@Input() currentStep: string;
+ isCloneSettingsActive: boolean;
+
onboardingSteps: OnboardingStepper[] = [
{
active: false,
@@ -83,25 +85,46 @@ export class OnboardingStepperComponent implements OnInit {
) { }
private updateActiveAndCompletedSteps(): void {
- this.onboardingSteps.forEach(step => {
- if (step.step === this.currentStep) {
- step.active = true;
- }
- });
- const onboardingState: OnboardingState = this.workspaceService.getOnboardingState();
+ if (this.currentStep === 'Clone Settings') {
+ this.isCloneSettingsActive = true;
+ this.onboardingSteps[0].completed = true;
+ this.onboardingSteps = [this.onboardingSteps[0]];
+ this.onboardingSteps.push(
+ {
+ active: true,
+ completed: false,
+ number: 6,
+ step: 'Complete the Configurations',
+ icon: 'advanced-setting',
+ route: 'clone_settings',
+ size: {
+ height: '20px',
+ width: '20px'
+ }
+ }
+ );
+ } else {
+ this.onboardingSteps.forEach(step => {
+ if (step.step === this.currentStep) {
+ step.active = true;
+ }
+ });
+
+ const onboardingState: OnboardingState = this.workspaceService.getOnboardingState();
- const onboardingStateStepMap = {
- [OnboardingState.CONNECTION]: 1,
- [OnboardingState.MAP_EMPLOYEES]: 2,
- [OnboardingState.EXPORT_SETTINGS]: 3,
- [OnboardingState.IMPORT_SETTINGS]: 4,
- [OnboardingState.ADVANCED_CONFIGURATION]: 5,
- [OnboardingState.COMPLETE]: 6
- };
+ const onboardingStateStepMap = {
+ [OnboardingState.CONNECTION]: 1,
+ [OnboardingState.MAP_EMPLOYEES]: 2,
+ [OnboardingState.EXPORT_SETTINGS]: 3,
+ [OnboardingState.IMPORT_SETTINGS]: 4,
+ [OnboardingState.ADVANCED_CONFIGURATION]: 5,
+ [OnboardingState.COMPLETE]: 6
+ };
- for (let index = onboardingStateStepMap[onboardingState] - 1; index > 0; index--) {
- this.onboardingSteps[index - 1].completed = true;
+ for (let index = onboardingStateStepMap[onboardingState] - 1; index > 0; index--) {
+ this.onboardingSteps[index - 1].completed = true;
+ }
}
}
@@ -114,5 +137,4 @@ export class OnboardingStepperComponent implements OnInit {
ngOnInit(): void {
this.updateActiveAndCompletedSteps();
}
-
}
diff --git a/src/app/shared/shared.module.ts b/src/app/shared/shared.module.ts
index a33eb1e1..acb566f9 100644
--- a/src/app/shared/shared.module.ts
+++ b/src/app/shared/shared.module.ts
@@ -24,6 +24,7 @@ import { MatLegacyChipInputEvent as MatChipInputEvent } from '@angular/material/
import { MatLegacyInputModule as MatInputModule } from '@angular/material/legacy-input';
import { MatDatepickerModule } from '@angular/material/datepicker';
+
// Pipes
import { TrimCharacterPipe } from './pipes/trim-character.pipe';
import { SearchPipe } from './pipes/search.pipe';
@@ -64,6 +65,9 @@ import { MandatoryErrorMessageComponent } from './components/helpers/mandatory-e
import { AddEmailDialogComponent } from './components/configuration/advanced-settings/add-email-dialog/add-email-dialog.component';
import { EmailMultiSelectFieldComponent } from './components/configuration/email-multi-select-field/email-multi-select-field.component';
import { SkipExportLogTableComponent } from './components/export-log/skip-export-log-table/skip-export-log-table.component';
+import { ToggleComponent } from './components/core/toggle/toggle.component';
+import { SelectComponent } from './components/core/select/select.component';
+import { EmailMultiSelectComponent } from '../core/email-multi-select/email-multi-select.component';
@NgModule({
declarations: [
@@ -104,7 +108,10 @@ import { SkipExportLogTableComponent } from './components/export-log/skip-export
MandatoryErrorMessageComponent,
AddEmailDialogComponent,
EmailMultiSelectFieldComponent,
- SkipExportLogTableComponent
+ SkipExportLogTableComponent,
+ ToggleComponent,
+ SelectComponent,
+ EmailMultiSelectComponent
],
imports: [
CommonModule,
@@ -158,7 +165,10 @@ import { SkipExportLogTableComponent } from './components/export-log/skip-export
DashboardResolveMappingErrorDialogComponent,
ExportLogChildTableComponent,
MandatoryErrorMessageComponent,
- MatChipsModule
+ MatChipsModule,
+ ToggleComponent,
+ SelectComponent,
+ EmailMultiSelectComponent
],
schemas: [CUSTOM_ELEMENTS_SCHEMA],
providers: [
diff --git a/src/assets/images/svgs/actions/arrow-mark-down-pink.svg b/src/assets/images/svgs/actions/arrow-mark-down-pink.svg
new file mode 100644
index 00000000..55911b42
--- /dev/null
+++ b/src/assets/images/svgs/actions/arrow-mark-down-pink.svg
@@ -0,0 +1,3 @@
+
diff --git a/src/assets/images/svgs/general/calendar-disabled.svg b/src/assets/images/svgs/general/calendar-disabled.svg
new file mode 100644
index 00000000..6cbbf2af
--- /dev/null
+++ b/src/assets/images/svgs/general/calendar-disabled.svg
@@ -0,0 +1,12 @@
+
diff --git a/src/assets/images/svgs/general/connect-arrow.svg b/src/assets/images/svgs/general/connect-arrow.svg
new file mode 100644
index 00000000..667a5bd2
--- /dev/null
+++ b/src/assets/images/svgs/general/connect-arrow.svg
@@ -0,0 +1,3 @@
+
diff --git a/src/assets/images/svgs/general/document-disabled.svg b/src/assets/images/svgs/general/document-disabled.svg
new file mode 100644
index 00000000..4b4802d2
--- /dev/null
+++ b/src/assets/images/svgs/general/document-disabled.svg
@@ -0,0 +1,6 @@
+
diff --git a/src/assets/images/svgs/general/employee-disabled.svg b/src/assets/images/svgs/general/employee-disabled.svg
new file mode 100644
index 00000000..b6d5c85d
--- /dev/null
+++ b/src/assets/images/svgs/general/employee-disabled.svg
@@ -0,0 +1,6 @@
+
diff --git a/src/assets/images/svgs/general/info-disabled.svg b/src/assets/images/svgs/general/info-disabled.svg
new file mode 100644
index 00000000..aa39e986
--- /dev/null
+++ b/src/assets/images/svgs/general/info-disabled.svg
@@ -0,0 +1,3 @@
+
diff --git a/src/assets/images/svgs/general/question-disabled.svg b/src/assets/images/svgs/general/question-disabled.svg
new file mode 100644
index 00000000..e746dc09
--- /dev/null
+++ b/src/assets/images/svgs/general/question-disabled.svg
@@ -0,0 +1,5 @@
+
diff --git a/src/assets/images/svgs/general/setting.svg b/src/assets/images/svgs/general/setting.svg
new file mode 100644
index 00000000..18fb8b0a
--- /dev/null
+++ b/src/assets/images/svgs/general/setting.svg
@@ -0,0 +1,14 @@
+
diff --git a/src/assets/images/svgs/general/sync-disabled.svg b/src/assets/images/svgs/general/sync-disabled.svg
new file mode 100644
index 00000000..04be8acf
--- /dev/null
+++ b/src/assets/images/svgs/general/sync-disabled.svg
@@ -0,0 +1,4 @@
+
diff --git a/src/assets/images/svgs/general/tabs-disabled.svg b/src/assets/images/svgs/general/tabs-disabled.svg
new file mode 100644
index 00000000..a8b3382d
--- /dev/null
+++ b/src/assets/images/svgs/general/tabs-disabled.svg
@@ -0,0 +1,4 @@
+
diff --git a/src/assets/images/svgs/general/tick-green.svg b/src/assets/images/svgs/general/tick-green.svg
new file mode 100644
index 00000000..c622e5da
--- /dev/null
+++ b/src/assets/images/svgs/general/tick-green.svg
@@ -0,0 +1,4 @@
+
diff --git a/src/styles.scss b/src/styles.scss
index a0f6ac22..71a28716 100644
--- a/src/styles.scss
+++ b/src/styles.scss
@@ -55,7 +55,7 @@ h3, h4, h5, h6 {
margin: 0;
}
-h1, h2 {
+h1, h2, ul {
margin: 0;
}
@@ -336,6 +336,13 @@ th.mat-header-cell:last-of-type {
transform: rotate(270deg);
}
}
+
+ &.clone-settings {
+ @extend .mat-tooltip, .above, .align-center;
+ white-space: normal !important;
+ margin-right: 6px !important;
+ max-width: 600px !important;
+ }
&.left {
overflow: initial;
@@ -448,6 +455,20 @@ th.mat-header-cell:last-of-type {
right: 2px !important;
}
+.mat-mdc-menu-panel {
+ margin-top: 10px !important;
+ margin-left: 10px !important;
+ min-height: 40px!important;
+}
+
+.mat-mdc-menu-item {
+ height: 24px!important;
+ font-size: 14px!important;
+ line-height: 0!important;
+ width: 230px!important;
+ min-height: 30px!important;
+}
+
.selected-value-check-mark {
width: 15px;
height: 15px;
@@ -460,6 +481,11 @@ th.mat-header-cell:last-of-type {
box-shadow: 0px 4px 4px rgba(44, 48, 78, 0.1);
}
+.cdk-overlay-dark-backdrop {
+ background: #2C304E;
+ opacity: 0.7 !important;
+}
+
.configuration {
&--section {
padding-bottom: 100px;
@@ -838,3 +864,119 @@ th.mat-header-cell:last-of-type {
}
}
}
+
+.import-settings {
+ &--field-checkbox-contents {
+ padding: 16px 32px 26px;
+ }
+
+ &--chart-of-account-header {
+ padding-right: 8px;
+ }
+
+ &--chart-of-account-list-section {
+ padding-top: 24px;
+ }
+
+ &--chart-of-account-list {
+ padding-right: 81.67px;
+ padding-bottom: 24px;
+ }
+
+ &--mapping-qbo-fyle-section {
+ width: 633.5px;
+ }
+
+ &--qbo-field {
+ background: #F5F5F5;
+ border: 1px solid #DFDFE2;
+ box-sizing: border-box;
+ border-radius: 4px;
+ width: 280px;
+ height: 36px;
+ }
+
+ &--qbo-field-text {
+ padding: 7px 161px 11px 17px;
+ }
+
+ &--fyle-field {
+ width: 305px;
+ height: 40px;
+ margin-top: -5px;
+ }
+
+ &--preview-text {
+ padding-top: 10px;
+ font-size: 12px;
+ }
+
+ &--preview-btn {
+ color: #0660F6;
+ }
+
+ &--fields-separator {
+ border-top: 1px solid #DFDFE2;
+ align-self: center;
+ width: 48px;
+ height: 26px;
+ }
+
+ &--or-text {
+ padding: 8px 16px;
+ }
+
+ &--field-label-section {
+ width: 650px;
+ padding-right: 46px;
+ }
+
+ &--create-custom-field {
+ padding: 8px 0px;
+ }
+
+ &--tax-section {
+ min-height: 102px;
+ }
+
+ &--default-tax-section {
+ height: 160px;
+ }
+
+ &--default-tax-contents {
+ padding: 16px 32px 40px;
+ }
+
+ &--default-tax-field {
+ padding-right: 729.5px;
+ }
+
+ &--default-tax-input {
+ width: 305px;
+ height: 40px;
+ }
+
+ &--default-tax-header {
+ padding-bottom: 6px;
+ }
+
+ &--default-tax-note {
+ padding-top: 12px;
+ }
+
+ &--field-label-note {
+ padding-top: 18px;
+ }
+
+ &--field {
+ background: #FAFCFF;
+ }
+
+ &--field-toggle-section {
+ padding: 30px 8px 30px 32px;
+ background: #FAFCFF;
+ box-sizing: border-box;
+ border: 1px solid #F5F5F5;
+ }
+
+}
\ No newline at end of file