diff --git a/src/app/integrations/qbo/qbo-shared/qbo-export-settings/qbo-export-settings.component.spec.ts b/src/app/integrations/qbo/qbo-shared/qbo-export-settings/qbo-export-settings.component.spec.ts index f92d1fc57..5e328461f 100644 --- a/src/app/integrations/qbo/qbo-shared/qbo-export-settings/qbo-export-settings.component.spec.ts +++ b/src/app/integrations/qbo/qbo-shared/qbo-export-settings/qbo-export-settings.component.spec.ts @@ -1,23 +1,841 @@ -import { ComponentFixture, TestBed } from '@angular/core/testing'; +/* eslint-disable max-lines */ +/* eslint-disable dot-notation */ +import { ComponentFixture, TestBed, discardPeriodicTasks, fakeAsync, flush, flushMicrotasks, tick } from '@angular/core/testing'; +import { FormBuilder, ReactiveFormsModule } from '@angular/forms'; +import { Router } from '@angular/router'; +import { of, throwError, Subject } from 'rxjs'; +import { MessageService } from 'primeng/api'; import { QboExportSettingsComponent } from './qbo-export-settings.component'; +import { HelperService } from 'src/app/core/services/common/helper.service'; +import { MappingService } from 'src/app/core/services/common/mapping.service'; +import { IntegrationsToastService } from 'src/app/core/services/common/integrations-toast.service'; +import { WindowService } from 'src/app/core/services/common/window.service'; +import { WorkspaceService } from 'src/app/core/services/common/workspace.service'; +import { + mockExportSettingsResponse, + mockBankAccounts, + mockCreditCardAccounts, + mockAccountsPayable, + mockVendors, + mockWorkspaceGeneralSettings, + mockPaginatedDestinationAttributes, + mockSaveResponse, + mockReimbursableExpenseGroupingDateOptions, + mockCCCExpenseGroupingDateOptions, + mockCCCExpenseGroupingDateOptionsForCreditDebit, + mockExportSettingOptionSearch, + mockExpenseAccountEvent, + mockGeneralEvent, + mockBrandingConfig, + mockExportSettingFormValueforNavigate, + mockDateOptionsforWatchers +} from '../../qbo.fixture'; +import { QBOExportSettingGet, QBOExportSettingModel } from 'src/app/core/models/qbo/qbo-configuration/qbo-export-setting.model'; +import { QboHelperService } from 'src/app/core/services/qbo/qbo-core/qbo-helper.service'; +import { QboExportSettingsService } from 'src/app/core/services/qbo/qbo-configuration/qbo-export-settings.service'; +import { ConfigurationWarningEvent, EmployeeFieldMapping, ExpenseGroupingFieldOption, NameInJournalEntry, QBOCorporateCreditCardExpensesObject, QboExportSettingDestinationOptionKey, QBOOnboardingState, QBOReimbursableExpensesObject, SplitExpenseGrouping, ToastSeverity } from 'src/app/core/models/enum/enum.model'; +import { ExportSettingModel, ExportSettingOptionSearch } from 'src/app/core/models/common/export-settings.model'; +import { DefaultDestinationAttribute, PaginatedDestinationAttribute } from 'src/app/core/models/db/destination-attribute.model'; +import { EventEmitter } from '@angular/core'; +import { consumerPollProducersForChange } from '@angular/core/primitives/signals'; +import { brandingFeatureConfig } from 'src/app/branding/branding-config'; +import { FeatureConfiguration } from 'src/app/core/models/branding/feature-configuration.model'; -xdescribe('QboExportSettingsComponent', () => { +describe('QboExportSettingsComponent', () => { let component: QboExportSettingsComponent; let fixture: ComponentFixture; + let exportSettingsServiceSpy: jasmine.SpyObj; + let helperServiceSpy: jasmine.SpyObj; + let qboHelperServiceSpy: jasmine.SpyObj; + let mappingServiceSpy: jasmine.SpyObj; + let workspaceServiceSpy: jasmine.SpyObj; + let windowServiceSpy: jasmine.SpyObj; + let integrationsToastServiceSpy: jasmine.SpyObj; + let routerSpy: jasmine.SpyObj; beforeEach(async () => { + const exportSettingsService = jasmine.createSpyObj('QboExportSettingsService', ['getExportSettings', 'setupDynamicValidators', 'setExportTypeValidatorsAndWatchers', 'postExportSettings'], { + creditCardExportTypeChange: new EventEmitter() + }); + const helperService = jasmine.createSpyObj('HelperService', ['addExportSettingFormValidator', 'setConfigurationSettingValidatorsAndWatchers', 'setOrClearValidators', 'addDefaultDestinationAttributeIfNotExists', 'enableFormField']); + const qboHelperService = jasmine.createSpyObj('QboHelperService', ['refreshQBODimensions']); + const mappingService = jasmine.createSpyObj('MappingService', ['getPaginatedDestinationAttributes']); + mappingService.getPaginatedDestinationAttributes.and.returnValue(of({})); + const workspaceService = jasmine.createSpyObj('WorkspaceService', ['getWorkspaceGeneralSettings']); + const windowService = { + get nativeWindow() { + return { + location: { + pathname: '/integrations/qbo/onboarding/export_settings' + } + }; + } + }; + const integrationsToastService = jasmine.createSpyObj('IntegrationsToastService', ['displayToastMessage']); + const router = jasmine.createSpyObj('Router', ['navigate']); await TestBed.configureTestingModule({ - declarations: [ QboExportSettingsComponent ] - }) - .compileComponents(); + declarations: [ QboExportSettingsComponent ], + imports: [ ReactiveFormsModule ], + providers: [ + FormBuilder, + { provide: QboExportSettingsService, useValue: exportSettingsService }, + { provide: HelperService, useValue: helperService }, + { provide: QboHelperService, useValue: qboHelperService }, + { provide: MappingService, useValue: mappingService }, + { provide: WorkspaceService, useValue: workspaceService }, + { provide: WindowService, useValue: windowService }, + { provide: IntegrationsToastService, useValue: integrationsToastService }, + { provide: MessageService, useValue: {} }, + { provide: Router, useValue: router }, + { provide: 'brandingFeatureConfig', useValue: mockBrandingConfig } + ] + }).compileComponents(); + exportSettingsServiceSpy = TestBed.inject(QboExportSettingsService) as jasmine.SpyObj; + helperServiceSpy = TestBed.inject(HelperService) as jasmine.SpyObj; + qboHelperServiceSpy = TestBed.inject(QboHelperService) as jasmine.SpyObj; + mappingServiceSpy = TestBed.inject(MappingService) as jasmine.SpyObj; + workspaceServiceSpy = TestBed.inject(WorkspaceService) as jasmine.SpyObj; + windowServiceSpy = TestBed.inject(WindowService) as jasmine.SpyObj; + integrationsToastServiceSpy = TestBed.inject(IntegrationsToastService) as jasmine.SpyObj; + routerSpy = TestBed.inject(Router) as jasmine.SpyObj; fixture = TestBed.createComponent(QboExportSettingsComponent); component = fixture.componentInstance; - fixture.detectChanges(); + + // Initialize the form + component.exportSettingForm = new FormBuilder().group({ + reimbursableExpenseObject: [mockExportSettingsResponse.workspace_general_settings.reimbursable_expenses_object], + corporateCreditCardExpenseObject: [mockExportSettingsResponse.workspace_general_settings.corporate_credit_card_expenses_object], + reimbursableExpense: [mockExportSettingsResponse.workspace_general_settings.reimbursable_expenses_object ? true : false], + reimbursableExportType: [mockExportSettingsResponse.workspace_general_settings.reimbursable_expenses_object], + employeeMapping: [EmployeeFieldMapping.EMPLOYEE], + expenseState: [mockExportSettingsResponse.expense_group_settings.expense_state], + cccExpenseState: [mockExportSettingsResponse.expense_group_settings.ccc_expense_state], + reimbursableExportGroup: [mockExportSettingsResponse.expense_group_settings.reimbursable_expense_group_fields[0]], + reimbursableExportDate: [mockExportSettingsResponse.expense_group_settings.reimbursable_export_date_type], + creditCardExportGroup: [mockExportSettingsResponse.expense_group_settings.corporate_credit_card_expense_group_fields[0]], + creditCardExpense: mockExportSettingsResponse.workspace_general_settings?.corporate_credit_card_expenses_object ? true : false, + creditCardExportDate: [mockExportSettingsResponse.expense_group_settings.ccc_export_date_type], + creditCardExportType: [mockExportSettingsResponse.workspace_general_settings.corporate_credit_card_expenses_object], + bankAccount: [mockExportSettingsResponse.general_mappings.bank_account], + defaultCCCAccount: [mockExportSettingsResponse.general_mappings.default_ccc_account], + accountsPayable: [mockExportSettingsResponse.general_mappings.accounts_payable], + defaultCreditCardVendor: [mockExportSettingsResponse.general_mappings.default_ccc_vendor], + nameInJournalEntry: [mockExportSettingsResponse.workspace_general_settings.name_in_journal_entry] + }); + }); + + describe('getSettingsAndSetupForm', () => { + beforeEach(() => { + exportSettingsServiceSpy.getExportSettings.and.returnValue(of(mockExportSettingsResponse)); + workspaceServiceSpy.getWorkspaceGeneralSettings.and.returnValue(of(mockWorkspaceGeneralSettings)); + mappingServiceSpy.getPaginatedDestinationAttributes.and.returnValues( + of(mockBankAccounts), + of(mockCreditCardAccounts), + of(mockAccountsPayable), + of(mockVendors) + ); + + spyOn(QBOExportSettingModel, 'mapAPIResponseToFormGroup').and.returnValue(component.exportSettingForm); + }); + + it('should fetch and set up export settings correctly', fakeAsync(() => { + component.ngOnInit(); + tick(); + + expect(component.exportSettings).toEqual(mockExportSettingsResponse); + expect(component.employeeFieldMapping).toEqual(mockWorkspaceGeneralSettings.employee_field_mapping); + expect(component.isImportItemsEnabled).toEqual(mockWorkspaceGeneralSettings.import_items); + + expect(component.exportSettingForm.controls.reimbursableExportDate.value).toEqual(mockExportSettingsResponse.expense_group_settings.reimbursable_export_date_type); + expect(component.exportSettingForm.controls.creditCardExportDate.value).toEqual(mockExportSettingsResponse.expense_group_settings.ccc_export_date_type); + + expect(component.isLoading).toBeFalse(); + })); + + it('should handle onboarding state correctly', fakeAsync(() => { + component.ngOnInit(); + tick(); + + expect(component.isOnboarding).toBeTrue(); + })); + + it('should set up expense accounts correctly', fakeAsync(() => { + component.ngOnInit(); + tick(); + + expect(component.expenseAccounts.length).toBe(mockBankAccounts.results.length + mockCreditCardAccounts.results.length); + })); + + it('should set up creditCardExpense correctly when reimbursableExpenses feature is disabled', fakeAsync(() => { + (component['brandingFeatureConfig'] as any) = { + featureFlags: { + exportSettings: { + reimbursableExpenses: false + } + } + }; + component.ngOnInit(); + tick(); + + expect(component.exportSettingForm.get('creditCardExpense')?.value).toBeTrue(); + })); + + it('should set up expense accounts correctly', fakeAsync(() => { + component.ngOnInit(); + tick(); + + expect(component.expenseAccounts).toBeDefined(); + expect(component.expenseAccounts.length).toBe(component.bankAccounts.length + component.cccAccounts.length); + })); + + it('should set up creditCardExpense correctly', fakeAsync(() => { + (component['brandingFeatureConfig'] as any) = { + featureFlags: { + exportSettings: { + reimbursableExpenses: false + } + } + }; + component.ngOnInit(); + tick(); + expect(component.creditCardExportTypes).toBeDefined(); + })); + + describe('getPaginatedDestinationAttributes', () => { + it('should return Observable of PaginatedDestinationAttribute', (done) => { + const mockResponse: PaginatedDestinationAttribute = mockPaginatedDestinationAttributes; + mappingServiceSpy.getPaginatedDestinationAttributes.and.returnValue(of(mockResponse)); + + component['getPaginatedAttributes'](QboExportSettingDestinationOptionKey.BANK_ACCOUNT, 'test').subscribe(result => { + expect(result).toEqual(mockResponse); + done(); + }); + }); + }); + + describe('handleExpenseAccountSearch', () => { + it('should trigger the option search flow for EXPENSE_ACCOUNT', fakeAsync(() => { + const mockSearchEvent = { + searchTerm: 'Savings', + destinationOptionKey: QboExportSettingDestinationOptionKey.EXPENSE_ACCOUNT, + destinationAttributes: [] + }; + + component.expenseAccounts = []; + component.bankAccounts = []; + component.cccAccounts = []; + + component['optionSearchUpdate'] = new Subject(); + + spyOn(component, 'getPaginatedAttributes').and.returnValues( + of(mockBankAccounts), + of(mockCreditCardAccounts) + ); + + spyOn(component, 'handleOptionSearch').and.callThrough(); + spyOn(component, 'handleExpenseAccountSearch').and.callThrough(); + spyOn(component, 'updateOptions').and.callThrough(); + spyOn(component, 'setUpdatedOptions').and.callThrough(); + spyOn(component, 'mergeOptions').and.callThrough(); + + // Subscribe to the optionSearchUpdate Subject + component['optionSearchUpdate'].subscribe((event) => { + component['handleOptionSearch'](event); + }); + + component.searchOptionsDropdown(mockSearchEvent); + tick(1000); + + expect(component['handleOptionSearch']).toHaveBeenCalledWith(mockSearchEvent); + })); + + it('should merge results from bank and credit card accounts', fakeAsync(() => { + spyOn(component as any, 'getPaginatedAttributes').and.returnValues(of(mockBankAccounts), of(mockCreditCardAccounts)); + spyOn(component as any, 'mergeOptions').and.callThrough(); + + component['handleExpenseAccountSearch'](mockExportSettingOptionSearch, []); + tick(); + + expect(component['mergeOptions']).toHaveBeenCalledWith( + jasmine.arrayContaining(mockBankAccounts.results.map(account => jasmine.objectContaining({ + id: account.destination_id, + name: account.value + }))), + jasmine.arrayContaining(mockCreditCardAccounts.results.map(account => jasmine.objectContaining({ + id: account.destination_id, + name: account.value + }))) + ); + })); + }); + + describe('handleGeneralOptionSearch', () => { + it('should call getPaginatedAttributes once for non-EXPENSE_ACCOUNT options', fakeAsync(() => { + spyOn(component as any, 'getPaginatedAttributes').and.returnValue(of({ results: [] })); + component['handleGeneralOptionSearch'](mockExportSettingOptionSearch, []); + tick(); + expect(component['getPaginatedAttributes']).toHaveBeenCalledWith(QboExportSettingDestinationOptionKey.BANK_ACCOUNT, 'anish'); + })); + + it('should update options and set them correctly', fakeAsync(() => { + spyOn(component as any, 'getPaginatedAttributes').and.returnValue(of(mockBankAccounts)); + spyOn(component as any, 'updateOptions').and.callThrough(); + spyOn(component as any, 'setUpdatedOptions').and.callThrough(); + + + component['handleGeneralOptionSearch'](mockExportSettingOptionSearch, []); + tick(); + + expect(component['setUpdatedOptions']).toHaveBeenCalledWith( + QboExportSettingDestinationOptionKey.BANK_ACCOUNT, + jasmine.arrayContaining(mockBankAccounts.results.map(account => jasmine.objectContaining({ + id: account.destination_id, + name: account.value + }))) + ); + })); + + it('should return correct existing options for different destination option keys', () => { + component.accountsPayables = mockAccountsPayable.results.map(account => ({ id: account.destination_id, name: account.value })); + component.bankAccounts = mockBankAccounts.results.map(account => ({ id: account.destination_id, name: account.value })); + component.cccAccounts = mockCreditCardAccounts.results.map(account => ({ id: account.destination_id, name: account.value })); + component.vendors = mockVendors.results.map(vendor => ({ id: vendor.destination_id, name: vendor.value })); + component.expenseAccounts = [...component.bankAccounts, ...component.cccAccounts]; + + expect(component['getExistingOptions'](QboExportSettingDestinationOptionKey.ACCOUNTS_PAYABLE)).toEqual(component.accountsPayables); + expect(component['getExistingOptions'](QboExportSettingDestinationOptionKey.BANK_ACCOUNT)).toEqual(component.bankAccounts); + expect(component['getExistingOptions'](QboExportSettingDestinationOptionKey.CREDIT_CARD_ACCOUNT)).toEqual(component.cccAccounts); + expect(component['getExistingOptions'](QboExportSettingDestinationOptionKey.VENDOR)).toEqual(component.vendors); + expect(component['getExistingOptions'](QboExportSettingDestinationOptionKey.EXPENSE_ACCOUNT)).toEqual(component.expenseAccounts); + expect(component['getExistingOptions']('INVALID_KEY' as QboExportSettingDestinationOptionKey)).toEqual([]); + }); + + it('should handle option search correctly for different destination option keys', fakeAsync(() => { + component.expenseAccounts = mockBankAccounts.results.map(account => ({ id: account.destination_id, name: account.value })); + component.bankAccounts = mockBankAccounts.results.map(account => ({ id: account.destination_id, name: account.value })); + + spyOn(component as any, 'getExistingOptions').and.callThrough(); + spyOn(component as any, 'handleExpenseAccountSearch'); + spyOn(component as any, 'handleGeneralOptionSearch'); + + component['handleOptionSearch'](mockExpenseAccountEvent); + tick(); + + expect(component['getExistingOptions']).toHaveBeenCalledWith(QboExportSettingDestinationOptionKey.EXPENSE_ACCOUNT); + expect(component['handleExpenseAccountSearch']).toHaveBeenCalledWith(mockExpenseAccountEvent, component.expenseAccounts); + + component['handleOptionSearch'](mockGeneralEvent); + tick(); + + expect(component['getExistingOptions']).toHaveBeenCalledWith(QboExportSettingDestinationOptionKey.BANK_ACCOUNT); + expect(component['handleGeneralOptionSearch']).toHaveBeenCalledWith(mockGeneralEvent, component.bankAccounts); + })); + + it('should update options correctly for all destination option keys', fakeAsync(() => { + const mockOptions = mockBankAccounts.results.map(account => ({ id: account.destination_id, name: account.value })); + + component.accountsPayables = []; + component.bankAccounts = []; + component.cccAccounts = []; + component.vendors = []; + component.expenseAccounts = []; + + spyOn(component, 'getPaginatedAttributes').and.returnValue(of(mockBankAccounts)); + + // Test for ACCOUNTS_PAYABLE + component['handleOptionSearch']({ + searchTerm: 'anish', + destinationOptionKey: QboExportSettingDestinationOptionKey.ACCOUNTS_PAYABLE, + destinationAttributes: [] + }); + tick(); + expect(component.accountsPayables).toEqual(mockOptions); + + // Test for BANK_ACCOUNT + component['handleOptionSearch']({ + searchTerm: 'anish', + destinationOptionKey: QboExportSettingDestinationOptionKey.BANK_ACCOUNT, + destinationAttributes: [] + }); + tick(); + expect(component.bankAccounts).toEqual(mockOptions); + + // Test for CREDIT_CARD_ACCOUNT + component['handleOptionSearch']({ + searchTerm: 'anish', + destinationOptionKey: QboExportSettingDestinationOptionKey.CREDIT_CARD_ACCOUNT, + destinationAttributes: [] + }); + tick(); + expect(component.cccAccounts).toEqual(mockOptions); + + // Test for VENDOR + component['handleOptionSearch']({ + searchTerm: 'anish', + destinationOptionKey: QboExportSettingDestinationOptionKey.VENDOR, + destinationAttributes: [] + }); + tick(); + expect(component.vendors).toEqual(mockOptions); + + // Test for EXPENSE_ACCOUNT + spyOn(component, 'handleExpenseAccountSearch').and.callFake(() => { + component.expenseAccounts = mockOptions; + }); + component['handleOptionSearch']({ + searchTerm: 'anish', + destinationOptionKey: QboExportSettingDestinationOptionKey.EXPENSE_ACCOUNT, + destinationAttributes: [] + }); + tick(); + expect(component.expenseAccounts).toEqual(mockOptions); + })); + }); }); - it('should create', () => { - expect(component).toBeTruthy(); + describe('save', () => { + beforeEach(() => { + // Initialize exportSettings with mock data + component.exportSettings = mockExportSettingsResponse; + workspaceServiceSpy.setOnboardingState = jasmine.createSpy('setOnboardingState'); + // Spy on the save method + spyOn(component, 'save').and.callThrough(); + }); + + it('should save export settings and navigate on success for onboarding', fakeAsync(() => { + exportSettingsServiceSpy.postExportSettings.and.returnValue(of(mockExportSettingsResponse)); + component.isOnboarding = true; + + component.exportSettingForm.patchValue(mockExportSettingFormValueforNavigate); + + component.save(); + tick(); + + expect(exportSettingsServiceSpy.postExportSettings).toHaveBeenCalled(); + expect(workspaceServiceSpy.setOnboardingState).toHaveBeenCalledWith(QBOOnboardingState.IMPORT_SETTINGS); + expect(routerSpy.navigate).toHaveBeenCalledWith(['/integrations/qbo/onboarding/import_settings']); + })); + + it('should handle error when saving export settings', fakeAsync(() => { + // Mock the postExportSettings to return an error + exportSettingsServiceSpy.postExportSettings.and.returnValue(throwError(() => new Error('API Error'))); + + // Set up the component state + component.exportSettings = mockExportSettingsResponse; + component.exportSettingForm.patchValue({ + reimbursableExportType: 'BILL', + creditCardExportType: 'CREDIT_CARD_PURCHASE' + }); + + // Spy on the isAdvancedSettingAffected method to return false + spyOn(component, 'isAdvancedSettingAffected').and.returnValue(false); + + // Call the save method + component.save(); + tick(); + + // Assert that the error handling is done correctly + expect(exportSettingsServiceSpy.postExportSettings).toHaveBeenCalled(); + expect(component.isSaveInProgress).toBeFalse(); + expect(integrationsToastServiceSpy.displayToastMessage).toHaveBeenCalledWith( + ToastSeverity.ERROR, + 'Error saving export settings, please try again later' + ); + })); + + it('should show warning dialog when advanced settings are affected', () => { + // Set up the component state to trigger the warning + component.exportSettings = mockExportSettingsResponse; + component.exportSettingForm.patchValue({ + reimbursableExportType: 'JOURNAL_ENTRY', + creditCardExportType: 'BILL' + }); + + // Spy on the isAdvancedSettingAffected method + spyOn(component, 'isAdvancedSettingAffected').and.returnValue(true); + + // Call the save method + component.save(); + + // Assert that the confirmation dialog is shown + expect(component.isConfirmationDialogVisible).toBeTrue(); + }); + + it('should navigate to advanced settings when isAdvancedSettingAffected returns true', fakeAsync(() => { + // Mock the initial export settings + component.exportSettings = mockExportSettingsResponse; + + // Set up the form with values that will trigger isAdvancedSettingAffected to return true + component.exportSettingForm.patchValue({ + reimbursableExportType: QBOReimbursableExpensesObject.JOURNAL_ENTRY, + creditCardExportType: QBOCorporateCreditCardExpensesObject.EXPENSE + }); + + // Spy on the isAdvancedSettingAffected method and its dependencies + spyOn(component, 'isAdvancedSettingAffected').and.callThrough(); + spyOn(component, 'isExportSettingsUpdated').and.returnValue(true); + spyOn(component, 'isSingleItemizedJournalEntryAffected').and.returnValue(true); + spyOn(component, 'isPaymentsSyncAffected').and.returnValue(false); + + // Spy on the constructWarningMessage method + spyOn(component, 'constructWarningMessage').and.returnValue('Warning message'); + + // Mock the postExportSettings to return an Observable with the correct type + exportSettingsServiceSpy.postExportSettings.and.returnValue(of(mockSaveResponse as QBOExportSettingGet)); + + // Call the save method + component.save(); + tick(); + + // Expect that isConfirmationDialogVisible is set to true + expect(component.isConfirmationDialogVisible).toBeTrue(); + + // Expect that warningDialogText is set + expect(component.warningDialogText).toBe('Warning message'); + + // Now simulate accepting the warning dialog + component.constructPayloadAndSave({ hasAccepted: true, event: ConfigurationWarningEvent.QBO_EXPORT_SETTINGS }); + + // Use tick to simulate the passage of time and allow any async operations to complete + tick(); + + // Flush any pending microtasks + flushMicrotasks(); + + // Expect that the postExportSettings was called + expect(exportSettingsServiceSpy.postExportSettings).toHaveBeenCalled(); + + // Expect that isSaveInProgress is set to false after save + expect(component.isSaveInProgress).toBeFalse(); + + // Expect that the router.navigate was called with the correct path + expect(routerSpy.navigate).toHaveBeenCalledWith(['/integrations/qbo/main/configuration/advanced_settings']); + + // Clean up + discardPeriodicTasks(); + })); + }); + + describe('searchOptionsDropdown', () => { + beforeEach(() => { + // Mock the Subject's next method and spy on it + component['optionSearchUpdate'] = new Subject(); + spyOn(component['optionSearchUpdate'], 'next'); + }); + + it('should set isOptionSearchInProgress to true and call optionSearchUpdate.next when searchTerm is present', () => { + const event: ExportSettingOptionSearch = { + searchTerm: 'anish', + destinationOptionKey: QboExportSettingDestinationOptionKey.BANK_ACCOUNT, + destinationAttributes: [] + }; + + component.searchOptionsDropdown(event); + + expect(component.isOptionSearchInProgress).toBe(true); + expect(component['optionSearchUpdate'].next).toHaveBeenCalledWith(event); + }); + + it('should not call optionSearchUpdate.next when searchTerm is empty', () => { + const event: ExportSettingOptionSearch = { + searchTerm: '', + destinationOptionKey: QboExportSettingDestinationOptionKey.BANK_ACCOUNT, + destinationAttributes: [] + }; + + component.searchOptionsDropdown(event); + + expect(component.isOptionSearchInProgress).toBeUndefined(); + expect(component['optionSearchUpdate'].next).not.toHaveBeenCalled(); + }); + + it('should call refreshQBODimensions when refreshDimensions is invoked', () => { + qboHelperServiceSpy.refreshQBODimensions.and.returnValue(of({})); // Mocking the Observable response with `of(null)` + + component.refreshDimensions(); + + expect(qboHelperServiceSpy.refreshQBODimensions).toHaveBeenCalled(); + }); + }); + + describe('updateCCCExpenseGroupingDateOptions', () => { + it('should update CCC expense grouping date options correctly', () => { + mappingServiceSpy.getPaginatedDestinationAttributes.and.returnValues( + of(mockBankAccounts), + of(mockCreditCardAccounts), + of(mockAccountsPayable), + of(mockVendors) + ); + // Testing CCC Expense Grouping Date Options + component.exportSettingForm.patchValue({ + creditCardExpense: true, + creditCardExportType: QBOCorporateCreditCardExpensesObject.CREDIT_CARD_PURCHASE + }); + fixture.detectChanges(); + component['updateCCCExpenseGroupingDateOptions'](QBOCorporateCreditCardExpensesObject.CREDIT_CARD_PURCHASE); + expect(component.exportSettingForm.get('creditCardExportType')?.value).toBe(QBOCorporateCreditCardExpensesObject.CREDIT_CARD_PURCHASE); + expect(component.cccExpenseGroupingDateOptions).toEqual(mockCCCExpenseGroupingDateOptionsForCreditDebit); + + // Test for CREDIT CARD PURCHASE + component.exportSettingForm.patchValue({ + creditCardExpense: true, + creditCardExportType: QBOCorporateCreditCardExpensesObject.DEBIT_CARD_EXPENSE + }); + fixture.detectChanges(); + component['updateCCCExpenseGroupingDateOptions'](QBOCorporateCreditCardExpensesObject.DEBIT_CARD_EXPENSE); + expect(component.cccExpenseGroupingDateOptions).toEqual(mockCCCExpenseGroupingDateOptionsForCreditDebit); + + // Test for Journal Entry + component.exportSettingForm.patchValue({ + creditCardExpense: true, + creditCardExportType: QBOCorporateCreditCardExpensesObject.JOURNAL_ENTRY + }); + fixture.detectChanges(); + component['updateCCCExpenseGroupingDateOptions'](QBOCorporateCreditCardExpensesObject.JOURNAL_ENTRY); + expect(component.cccExpenseGroupingDateOptions).toEqual(mockCCCExpenseGroupingDateOptions); + + const creditCardExportGroup = component.exportSettingForm.get('creditCardExportGroup'); + expect(creditCardExportGroup?.value).toBe(ExpenseGroupingFieldOption.EXPENSE_ID); + expect(creditCardExportGroup?.disabled).toBeTrue(); + }); + }); + + describe('constructWarningMessage', () => { + beforeEach(() => { + component.exportSettings = { + workspace_general_settings: { + reimbursable_expenses_object: 'EXPENSE', + corporate_credit_card_expenses_object: 'CREDIT_CARD_PURCHASE' + } + } as any; + component.exportSettingForm.patchValue({ + reimbursableExportType: 'BILL', + creditCardExportType: 'JOURNAL_ENTRY' + }); + }); + + it('should construct warning message for reimbursable expenses when single itemized journal entry is affected', () => { + spyOn(component, 'isSingleItemizedJournalEntryAffected').and.returnValue(true); + spyOn(component, 'isPaymentsSyncAffected').and.returnValue(false); + spyOn(component, 'replaceContentBasedOnConfiguration').and.callThrough(); + + const result = component['constructWarningMessage'](); + + expect(component['replaceContentBasedOnConfiguration']).toHaveBeenCalledWith('BILL', 'EXPENSE', 'reimbursable'); + expect(result).toContain('You have changed the export type of reimbursable expense from Expense to Bill'); + }); + + it('should construct warning message for credit card expenses when single itemized journal entry is affected', () => { + component.exportSettingForm.patchValue({ reimbursableExportType: 'EXPENSE' }); + spyOn(component, 'isSingleItemizedJournalEntryAffected').and.returnValue(true); + spyOn(component, 'isPaymentsSyncAffected').and.returnValue(false); + const result = component['constructWarningMessage'](); + expect(result).toContain('You have changed the export type of credit card expense from Credit_card_purchase to Journal_entry'); + }); + + it('should construct warning message when payments sync is affected', () => { + spyOn(component, 'isSingleItemizedJournalEntryAffected').and.returnValue(false); + spyOn(component, 'isPaymentsSyncAffected').and.returnValue(true); + const result = component['constructWarningMessage'](); + expect(result).toContain('You have changed the export type of reimbursable expense from Expense to Bill'); + }); + + it('should return empty string when no settings are affected', () => { + spyOn(component, 'isSingleItemizedJournalEntryAffected').and.returnValue(false); + spyOn(component, 'isPaymentsSyncAffected').and.returnValue(false); + const result = component['constructWarningMessage'](); + expect(result).toBe(''); + }); + }); + + describe('replaceContentBasedOnConfiguration', () => { + it('should return correct content for configuration update', () => { + const result = component['replaceContentBasedOnConfiguration']('BILL', 'EXPENSE', 'reimbursable'); + expect(result).toContain('You have changed the export type of reimbursable expense from Expense to Bill'); + }); + + it('should return correct content for new configuration', () => { + const result = component['replaceContentBasedOnConfiguration']('BILL', 'None', 'credit card'); + expect(result).toContain('You have selected a new export type for the credit card expense'); + }); + + it('should include additional content when updated to JOURNAL_ENTRY and isImportItemsEnabled is true', () => { + component.isImportItemsEnabled = true; + const result = component['replaceContentBasedOnConfiguration'](QBOReimbursableExpensesObject.JOURNAL_ENTRY, 'EXPENSE', 'reimbursable'); + expect(result).toContain('Products/services previously imported as categories'); + }); + }); + + describe('setupCustomWatchers', () => { + beforeEach(() => { + const exportSettingsService = jasmine.createSpyObj('QboExportSettingsService', ['getExportSettings', 'setupDynamicValidators', 'setExportTypeValidatorsAndWatchers', 'postExportSettings'], { + creditCardExportTypeChange: jasmine.createSpyObj('EventEmitter', ['emit']) as EventEmitter + }); + }); + + it('should call updateCCCExpenseGroupingDateOptions if creditCardExportType is CREDIT_CARD_PURCHASE', () => { + // Restore the original method + delete (component as any).setupCustomWatchers; + + // Spy on the updateCCCExpenseGroupingDateOptions method + spyOn(component, 'updateCCCExpenseGroupingDateOptions'); + + // Set up the form with the required value + component.exportSettingForm.patchValue({ + creditCardExportType: QBOCorporateCreditCardExpensesObject.CREDIT_CARD_PURCHASE + }); + + // Call the method + component['setupCustomWatchers'](); + + // Check if updateCCCExpenseGroupingDateOptions was called with the correct parameter + expect(component['updateCCCExpenseGroupingDateOptions']).toHaveBeenCalledWith(QBOCorporateCreditCardExpensesObject.CREDIT_CARD_PURCHASE); + }); + + it('should handle creditCardExportTypeChange subscription correctly', () => { + spyOn(component, 'setupCustomWatchers').and.callThrough(); + + // Spy on the updateCCCExpenseGroupingDateOptions method + spyOn(component, 'updateCCCExpenseGroupingDateOptions'); + + // Call the method + component['setupCustomWatchers'](); + + // Emit JOURNAL_ENTRY + exportSettingsServiceSpy.creditCardExportTypeChange.next(QBOCorporateCreditCardExpensesObject.JOURNAL_ENTRY); + + // Check if showNameInJournalOption is set to true + expect(component.showNameInJournalOption).toBeTrue(); + + // Check if updateCCCExpenseGroupingDateOptions was called with JOURNAL_ENTRY + expect(component['updateCCCExpenseGroupingDateOptions']).toHaveBeenCalledWith(QBOCorporateCreditCardExpensesObject.JOURNAL_ENTRY); + + // Reset the spy + (component['updateCCCExpenseGroupingDateOptions'] as jasmine.Spy).calls.reset(); + + // Emit CREDIT_CARD_PURCHASE + exportSettingsServiceSpy.creditCardExportTypeChange.next(QBOCorporateCreditCardExpensesObject.CREDIT_CARD_PURCHASE); + + // Check if showNameInJournalOption is set to false + expect(component.showNameInJournalOption).toBeFalse(); + + // Check if updateCCCExpenseGroupingDateOptions was called with CREDIT_CARD_PURCHASE + expect(component['updateCCCExpenseGroupingDateOptions']).toHaveBeenCalledWith(QBOCorporateCreditCardExpensesObject.CREDIT_CARD_PURCHASE); + }); + }); + + describe('setupCustomDateOptionWatchers', () => { + beforeEach(() => { + component.exportSettingForm.patchValue({ + reimbursableExportGroup: '', + creditCardExportType: QBOCorporateCreditCardExpensesObject.JOURNAL_ENTRY, + creditCardExportGroup: '' + }); + component.reimbursableExpenseGroupingDateOptions = []; + component.cccExpenseGroupingDateOptions = []; + + spyOn(component, 'setupCustomDateOptionWatchers').and.callThrough(); + + spyOn(QBOExportSettingModel, 'getReimbursableExpenseGroupingDateOptions').and.returnValue([]); + spyOn(ExportSettingModel, 'constructGroupingDateOptions').and.returnValue([]); + spyOn(component, 'updateCCCExpenseGroupingDateOptions'); + }); + + it('should update reimbursableExpenseGroupingDateOptions when reimbursableExportGroup changes', () => { + component['setupCustomDateOptionWatchers'](); + + component.exportSettingForm.patchValue({ + reimbursableExportGroup: ExpenseGroupingFieldOption.EXPENSE_ID + }); + + expect(component.reimbursableExpenseGroupingDateOptions).toEqual([]); + }); + + it('should call updateCCCExpenseGroupingDateOptions when creditCardExportType changes', () => { + component['setupCustomDateOptionWatchers'](); + + component.exportSettingForm.patchValue({ + creditCardExportType: QBOCorporateCreditCardExpensesObject.BILL + }); + + expect(component.cccExpenseGroupingDateOptions).toEqual([]); + }); + + it('should update cccExpenseGroupingDateOptions when creditCardExportGroup changes', () => { + component['setupCustomDateOptionWatchers'](); + + component.exportSettingForm.patchValue({ + creditCardExportType: QBOCorporateCreditCardExpensesObject.BILL, + creditCardExportGroup: ExpenseGroupingFieldOption.EXPENSE_ID + }); + + expect(component.cccExpenseGroupingDateOptions).toEqual([]); + }); + }); + + describe('optionSearchWatcher', () => { + beforeEach(() => { + // Restore the original method + spyOn(component, 'optionSearchWatcher').and.callThrough(); + + // Create a new Subject for optionSearchUpdate + component['optionSearchUpdate'] = new Subject(); + + // Spy on handleOptionSearch method + spyOn(component, 'handleOptionSearch'); + }); + + afterEach(() => { + // Complete the subject to clean up any pending observables + component['optionSearchUpdate'].complete(); + }); + + it('should call handleOptionSearch after debounce time when optionSearchUpdate emits', fakeAsync(() => { + // Call the method to set up the watcher + component['optionSearchWatcher'](); + + // Emit the event + component['optionSearchUpdate'].next(mockExportSettingOptionSearch); + + // Fast-forward time by 1000ms + tick(1000); + + // Now expect handleOptionSearch to have been called with the mock event + expect(component['handleOptionSearch']).toHaveBeenCalledWith(mockExportSettingOptionSearch); + + // Discard any remaining periodic tasks + discardPeriodicTasks(); + })); + + it('should not call handleOptionSearch before debounce time', fakeAsync(() => { + component['optionSearchWatcher'](); + + component['optionSearchUpdate'].next(mockExportSettingOptionSearch); + + // Fast-forward time by 999ms + tick(999); + + // Expect handleOptionSearch not to have been called + expect(component['handleOptionSearch']).not.toHaveBeenCalled(); + + // Discard any remaining periodic tasks + discardPeriodicTasks(); + })); + }); + + describe('navigateToPreviousStep', () => { + it('should navigate to employee_settings when mapEmployees feature flag is true', () => { + mockBrandingConfig.featureFlags.mapEmployees = true; + component.navigateToPreviousStep(); + expect(routerSpy.navigate).toHaveBeenCalledWith(['/integrations/qbo/onboarding/employee_settings']); + }); + + xit('should navigate to connector when mapEmployees feature flag is false', () => { + mockBrandingConfig.featureFlags.mapEmployees = false; + component.navigateToPreviousStep(); + expect(routerSpy.navigate).toHaveBeenCalledWith(['/integrations/qbo/onboarding/connector']); + }); }); }); diff --git a/src/app/integrations/qbo/qbo-shared/qbo-export-settings/qbo-export-settings.component.ts b/src/app/integrations/qbo/qbo-shared/qbo-export-settings/qbo-export-settings.component.ts index a91eecc1e..3aa45d340 100644 --- a/src/app/integrations/qbo/qbo-shared/qbo-export-settings/qbo-export-settings.component.ts +++ b/src/app/integrations/qbo/qbo-shared/qbo-export-settings/qbo-export-settings.component.ts @@ -289,92 +289,92 @@ export class QboExportSettingsComponent implements OnInit { this.optionSearchUpdate.pipe( debounceTime(1000) ).subscribe((event: ExportSettingOptionSearch) => { - let existingOptions: DefaultDestinationAttribute[] = []; - - switch (event.destinationOptionKey) { - case QboExportSettingDestinationOptionKey.ACCOUNTS_PAYABLE: - existingOptions = this.accountsPayables; - break; - case QboExportSettingDestinationOptionKey.BANK_ACCOUNT: - existingOptions = this.bankAccounts; - break; - case QboExportSettingDestinationOptionKey.CREDIT_CARD_ACCOUNT: - existingOptions = this.cccAccounts; - break; - case QboExportSettingDestinationOptionKey.VENDOR: - existingOptions = this.vendors; - break; - case QboExportSettingDestinationOptionKey.EXPENSE_ACCOUNT: - existingOptions = this.expenseAccounts; - } - - let newOptions: DefaultDestinationAttribute[]; + this.handleOptionSearch(event); + }); + } - // Handle EXPENSE_ACCOUNT events with 2 paginated_destination_attributes calls instead of 1 - if (event.destinationOptionKey === QboExportSettingDestinationOptionKey.EXPENSE_ACCOUNT) { - forkJoin([ - QboExportSettingDestinationOptionKey.BANK_ACCOUNT, - QboExportSettingDestinationOptionKey.CREDIT_CARD_ACCOUNT - ].map(attributeType => - this.mappingService.getPaginatedDestinationAttributes(attributeType, event.searchTerm) - .pipe(filter(response => !!response)) - )).subscribe(([bankAccounts, cccAccounts]) => { - const expenseAccounts = bankAccounts.results.concat(cccAccounts.results); + private handleOptionSearch(event: ExportSettingOptionSearch): void { + const existingOptions = this.getExistingOptions(event.destinationOptionKey as QboExportSettingDestinationOptionKey); - // Convert DestinationAttributes to DefaultDestinationAttributes (name, id) - newOptions = expenseAccounts.map(QBOExportSettingModel.formatGeneralMappingPayload); + if (event.destinationOptionKey === QboExportSettingDestinationOptionKey.EXPENSE_ACCOUNT) { + this.handleExpenseAccountSearch(event, existingOptions); + } else { + this.handleGeneralOptionSearch(event, existingOptions); + } + } - // Insert new options to existing options - newOptions.forEach((option) => { - if (!existingOptions.find((existingOption) => existingOption.id === option.id)) { - existingOptions.push(option); - } - }); + private getExistingOptions(destinationOptionKey: QboExportSettingDestinationOptionKey): DefaultDestinationAttribute[] { + switch (destinationOptionKey) { + case QboExportSettingDestinationOptionKey.ACCOUNTS_PAYABLE: + return this.accountsPayables; + case QboExportSettingDestinationOptionKey.BANK_ACCOUNT: + return this.bankAccounts; + case QboExportSettingDestinationOptionKey.CREDIT_CARD_ACCOUNT: + return this.cccAccounts; + case QboExportSettingDestinationOptionKey.VENDOR: + return this.vendors; + case QboExportSettingDestinationOptionKey.EXPENSE_ACCOUNT: + return this.expenseAccounts; + default: + return []; + } + } - this.expenseAccounts = existingOptions.concat(); - this.expenseAccounts.sort((a, b) => (a.name || '').localeCompare(b.name || '')); + private handleExpenseAccountSearch(event: ExportSettingOptionSearch, existingOptions: DefaultDestinationAttribute[]): void { + forkJoin([ + this.getPaginatedAttributes(QboExportSettingDestinationOptionKey.BANK_ACCOUNT, event.searchTerm), + this.getPaginatedAttributes(QboExportSettingDestinationOptionKey.CREDIT_CARD_ACCOUNT, event.searchTerm) + ]).subscribe(([bankAccounts, cccAccounts]) => { + const expenseAccounts = bankAccounts.results.concat(cccAccounts.results); + this.updateOptions(QboExportSettingDestinationOptionKey.EXPENSE_ACCOUNT, expenseAccounts, existingOptions); + }); + } - this.isOptionSearchInProgress = false; - }); + private handleGeneralOptionSearch(event: ExportSettingOptionSearch, existingOptions: DefaultDestinationAttribute[]): void { + this.getPaginatedAttributes(event.destinationOptionKey as QboExportSettingDestinationOptionKey, event.searchTerm).subscribe((response) => { + this.updateOptions(event.destinationOptionKey as QboExportSettingDestinationOptionKey, response.results, existingOptions); + }); + } - return; - } + private getPaginatedAttributes(destinationOptionKey: QboExportSettingDestinationOptionKey, searchTerm: string): Observable { + return this.mappingService.getPaginatedDestinationAttributes(destinationOptionKey, searchTerm) + .pipe(filter(response => !!response)); + } - this.mappingService.getPaginatedDestinationAttributes(event.destinationOptionKey, event.searchTerm).subscribe((response) => { - - // Convert DestinationAttributes to DefaultDestinationAttributes (name, id) - newOptions = response.results.map(QBOExportSettingModel.formatGeneralMappingPayload); - - // Insert new options to existing options - newOptions.forEach((option) => { - if (!existingOptions.find((existingOption) => existingOption.id === option.id)) { - existingOptions.push(option); - } - }); - - - switch (event.destinationOptionKey) { - case QboExportSettingDestinationOptionKey.ACCOUNTS_PAYABLE: - this.accountsPayables = existingOptions.concat(); - this.accountsPayables.sort((a, b) => (a.name || '').localeCompare(b.name || '')); - break; - case QboExportSettingDestinationOptionKey.BANK_ACCOUNT: - this.bankAccounts = existingOptions.concat(); - this.bankAccounts.sort((a, b) => (a.name || '').localeCompare(b.name || '')); - break; - case QboExportSettingDestinationOptionKey.CREDIT_CARD_ACCOUNT: - this.cccAccounts = existingOptions.concat(); - this.cccAccounts.sort((a, b) => (a.name || '').localeCompare(b.name || '')); - break; - case QboExportSettingDestinationOptionKey.VENDOR: - this.vendors = existingOptions.concat(); - this.vendors.sort((a, b) => (a.name || '').localeCompare(b.name || '')); - break; - } + private updateOptions(destinationOptionKey: QboExportSettingDestinationOptionKey, newResults: any[], existingOptions: DefaultDestinationAttribute[]): void { + const newOptions = newResults.map(QBOExportSettingModel.formatGeneralMappingPayload); + const updatedOptions = this.mergeOptions(existingOptions, newOptions); + this.setUpdatedOptions(destinationOptionKey, updatedOptions); + this.isOptionSearchInProgress = false; + } - this.isOptionSearchInProgress = false; - }); + private mergeOptions(existingOptions: DefaultDestinationAttribute[], newOptions: DefaultDestinationAttribute[]): DefaultDestinationAttribute[] { + newOptions.forEach((option) => { + if (!existingOptions.find((existingOption) => existingOption.id === option.id)) { + existingOptions.push(option); + } }); + return existingOptions.concat().sort((a, b) => (a.name || '').localeCompare(b.name || '')); + } + + private setUpdatedOptions(destinationOptionKey: QboExportSettingDestinationOptionKey, updatedOptions: DefaultDestinationAttribute[]): void { + switch (destinationOptionKey) { + case QboExportSettingDestinationOptionKey.ACCOUNTS_PAYABLE: + this.accountsPayables = updatedOptions; + break; + case QboExportSettingDestinationOptionKey.BANK_ACCOUNT: + this.bankAccounts = updatedOptions; + break; + case QboExportSettingDestinationOptionKey.CREDIT_CARD_ACCOUNT: + this.cccAccounts = updatedOptions; + break; + case QboExportSettingDestinationOptionKey.VENDOR: + this.vendors = updatedOptions; + break; + case QboExportSettingDestinationOptionKey.EXPENSE_ACCOUNT: + this.expenseAccounts = updatedOptions; + break; + } } searchOptionsDropdown(event: ExportSettingOptionSearch): void { diff --git a/src/app/integrations/qbo/qbo.fixture.ts b/src/app/integrations/qbo/qbo.fixture.ts index eeaad9bd2..92080964b 100644 --- a/src/app/integrations/qbo/qbo.fixture.ts +++ b/src/app/integrations/qbo/qbo.fixture.ts @@ -1,6 +1,9 @@ import { MinimalUser } from "src/app/core/models/db/user.model"; -import { AutoMapEmployeeOptions, EmployeeFieldMapping, QBOOnboardingState, QBOReimbursableExpensesObject } from "src/app/core/models/enum/enum.model"; +import { AutoMapEmployeeOptions, EmployeeFieldMapping, CCCExpenseState, ExpenseState, ExportDateType, NameInJournalEntry, QBOCorporateCreditCardExpensesObject, QBOOnboardingState, SplitExpenseGrouping, QBOReimbursableExpensesObject, QboExportSettingDestinationOptionKey } from "src/app/core/models/enum/enum.model"; import { QBOEmployeeSettingGet, QBOEmployeeSettingPost } from "src/app/core/models/qbo/qbo-configuration/qbo-employee-setting.model"; +import { PaginatedDestinationAttribute } from 'src/app/core/models/db/destination-attribute.model'; +import { SelectFormOption } from "src/app/core/models/common/select-form-option.model"; +import { ExportSettingOptionSearch } from "src/app/core/models/common/export-settings.model"; export const mockUser: MinimalUser = { org_id: '123', @@ -90,12 +93,6 @@ export const mockEmployeeSettingResponse: QBOEmployeeSettingGet = { } ]; - export const mockExportSettings = { - workspace_general_settings: { - reimbursable_expenses_object: QBOReimbursableExpensesObject.BILL - } - }; - export const employeeSettingsPayload: QBOEmployeeSettingPost = { workspace_general_settings: { employee_field_mapping: EmployeeFieldMapping.VENDOR, @@ -109,4 +106,649 @@ export const mockEmployeeSettingResponse: QBOEmployeeSettingGet = { auto_map_employees: AutoMapEmployeeOptions.NAME }, workspace_id: 1 - }; \ No newline at end of file + }; + +export const mockExportSettings = { + "workspace_general_settings": { + "reimbursable_expenses_object": QBOReimbursableExpensesObject.BILL, + "corporate_credit_card_expenses_object": QBOCorporateCreditCardExpensesObject.BILL, + "is_simplify_report_closure_enabled": true, + "name_in_journal_entry": NameInJournalEntry.EMPLOYEE + }, + "expense_group_settings": { + "reimbursable_expense_group_fields": [ + "fund_source", + "expense_id", + "employee_email", + "spent_at" + ], + "corporate_credit_card_expense_group_fields": [ + "claim_number", + "fund_source", + "employee_email", + "verified_at", + "report_id" + ], + "expense_state": ExpenseState.PAYMENT_PROCESSING, + "ccc_expense_state": CCCExpenseState.APPROVED, + "reimbursable_export_date_type": ExportDateType.SPENT_AT, + "ccc_export_date_type": ExportDateType.VERIFIED_AT, + "split_expense_grouping": SplitExpenseGrouping.MULTIPLE_LINE_ITEM + }, + "general_mappings": { + "accounts_payable": { + "name": "Accounts Payable (A/P) 2", + "id": "91" + }, + "qbo_expense_account": { + "name": null, + "id": null + }, + "bank_account": { + "name": "Checking Debit Card", + "id": "131" + }, + "default_ccc_account": { + "name": null, + "id": null + }, + "default_debit_card_account": { + "name": null, + "id": null + }, + "default_ccc_vendor": { + "name": "Ashwin from NetSuite", + "id": "110" + } + }, + "workspace_id": 512 +}; + +export const mockReimbursableExpenseGroupingDateOptions: SelectFormOption[] = [ + { + 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 mockCCCExpenseGroupingDateOptions: SelectFormOption[] = [ + { + label: 'Export 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 mockCCCExpenseGroupingDateOptionsForCreditDebit: SelectFormOption[] = [ + { + label: 'Card Transaction Post Date', + value: ExportDateType.POSTED_AT + }, + { + label: 'Spend date', + value: ExportDateType.SPENT_AT + } +]; + +export const mockPaginatedDestinationAttributes = { + "count": 5, + "next": null, + "previous": null, + "results": [ + { + "id": 253175, + "attribute_type": "BANK_ACCOUNT", + "display_name": "Bank Account", + "value": "Ashwin Bankkkk", + "destination_id": "167", + "auto_created": false, + "active": true, + "detail": { + "account_type": "Bank", + "fully_qualified_name": "Ashwin Bankkkk" + }, + "code": 'anish', + "created_at": new Date("2024-08-22T06:44:11.074373Z"), + "updated_at": new Date("2024-08-22T06:44:11.074401Z"), + "workspace": 512 + }, + { + "id": 253176, + "attribute_type": "BANK_ACCOUNT", + "display_name": "Bank Account", + "value": "Checking", + "destination_id": "35", + "auto_created": false, + "active": true, + "detail": { + "account_type": "Bank", + "fully_qualified_name": "Checking" + }, + "code": "anish", + "created_at": new Date("2024-08-22T06:44:11.074447Z"), + "updated_at": new Date("2024-08-22T06:44:11.074458Z"), + "workspace": 512 + }, + { + "id": 253177, + "attribute_type": "BANK_ACCOUNT", + "display_name": "Bank Account", + "value": "Checking Debit Card", + "destination_id": "131", + "auto_created": false, + "active": true, + "detail": { + "account_type": "Bank", + "fully_qualified_name": "Checking Debit Card" + }, + "code": "anish", + "created_at": new Date("2024-08-22T06:44:11.074498Z"), + "updated_at": new Date("2024-08-22T06:44:11.074508Z"), + "workspace": 512 + }, + { + "id": 253178, + "attribute_type": "BANK_ACCOUNT", + "display_name": "Bank Account", + "value": "Gayathiri", + "destination_id": "128", + "auto_created": false, + "active": true, + "detail": { + "account_type": "Bank", + "fully_qualified_name": "Gayathiri" + }, + "code": "anish", + "created_at": new Date("2024-08-22T06:44:11.074544Z"), + "updated_at": new Date("2024-08-22T06:44:11.074554Z"), + "workspace": 512 + }, + { + "id": 253179, + "attribute_type": "BANK_ACCOUNT", + "display_name": "Bank Account", + "value": "Savings", + "destination_id": "36", + "auto_created": false, + "active": true, + "detail": { + "account_type": "Bank", + "fully_qualified_name": "Savings" + }, + "code": "anish", + "created_at": new Date("2024-08-22T06:44:11.074591Z"), + "updated_at": new Date("2024-08-22T06:44:11.074601Z"), + "workspace": 512 + } + ] +}; + +export const mockWorkspaceGeneralSettings = { + id: 684, + reimbursable_expenses_object: QBOReimbursableExpensesObject.BILL, + corporate_credit_card_expenses_object: QBOCorporateCreditCardExpensesObject.BILL, + employee_field_mapping: EmployeeFieldMapping.VENDOR, + map_merchant_to_vendor: true, + import_categories: true, + import_items: false, + import_projects: false, + import_tax_codes: false, + change_accounting_period: false, + charts_of_accounts: [ + 'Expense' + ], + memo_structure: [ + 'employee_email', + 'purpose', + 'category', + 'spent_on', + 'report_number', + 'expense_link' + ], + auto_map_employees: AutoMapEmployeeOptions.NAME, + auto_create_destination_entity: false, + auto_create_merchants_as_vendors: false, + sync_fyle_to_qbo_payments: false, + sync_qbo_to_fyle_payments: true, + is_simplify_report_closure_enabled: true, + category_sync_version: 'v2', + je_single_credit_line: false, + map_fyle_cards_qbo_account: false, + skip_cards_mapping: false, + import_vendors_as_merchants: false, + is_multi_currency_allowed: false, + is_tax_override_enabled: true, + name_in_journal_entry: NameInJournalEntry.EMPLOYEE, + created_at: new Date('2024-08-22T08:50:29.978051Z'), + updated_at: new Date('2024-09-02T17:28:32.233045Z'), + workspace: 512 +}; + +export const mockBankAccounts: PaginatedDestinationAttribute = { + count: 5, + next: null, + previous: null, + results: [ + { + id: 253175, + attribute_type: 'BANK_ACCOUNT', + display_name: 'Bank Account', + value: 'Ashwin Bankkkk', + destination_id: '167', + active: true, + created_at: new Date("2024-08-22T06:44:11.074373Z"), + updated_at: new Date("2024-08-22T06:44:11.074401Z"), + workspace: 512 + }, + { + id: 253176, + attribute_type: 'BANK_ACCOUNT', + display_name: 'Bank Account', + value: 'Checking', + destination_id: '35', + active: true, + created_at: new Date("2024-08-22T06:44:11.074447Z"), + updated_at: new Date("2024-08-22T06:44:11.074458Z"), + workspace: 512 + }, + { + id: 253177, + attribute_type: 'BANK_ACCOUNT', + display_name: 'Bank Account', + value: 'Checking Debit Card', + destination_id: '131', + active: true, + created_at: new Date("2024-08-22T06:44:11.074498Z"), + updated_at: new Date("2024-08-22T06:44:11.074508Z"), + workspace: 512 + }, + { + id: 253178, + attribute_type: 'BANK_ACCOUNT', + display_name: 'Bank Account', + value: 'Gayathiri', + destination_id: '128', + active: true, + created_at: new Date("2024-08-22T06:44:11.074544Z"), + updated_at: new Date("2024-08-22T06:44:11.074554Z"), + workspace: 512 + }, + { + id: 253179, + attribute_type: 'BANK_ACCOUNT', + display_name: 'Bank Account', + value: 'Savings', + destination_id: '36', + active: true, + created_at: new Date("2024-08-22T06:44:11.074591Z"), + updated_at: new Date("2024-08-22T06:44:11.074601Z"), + workspace: 512 + } + ] +}; + +export const mockCreditCardAccounts: PaginatedDestinationAttribute = { + count: 3, + next: null, + previous: null, + results: [ + { + id: 253172, + attribute_type: 'CREDIT_CARD_ACCOUNT', + display_name: 'Credit Card Account', + value: 'Mastercard', + destination_id: '41', + active: true, + created_at: new Date("2024-08-22T06:44:11.012441Z"), + updated_at: new Date("2024-08-22T06:44:11.012465Z"), + workspace: 512 + }, + { + id: 253173, + attribute_type: 'CREDIT_CARD_ACCOUNT', + display_name: 'Credit Card Account', + value: 'QBO CCC Support Account', + destination_id: '130', + active: true, + created_at: new Date("2024-08-22T06:44:11.012505Z"), + updated_at: new Date("2024-08-22T06:44:11.012514Z"), + workspace: 512 + }, + { + id: 253174, + attribute_type: 'CREDIT_CARD_ACCOUNT', + display_name: 'Credit Card Account', + value: 'Visa', + destination_id: '42', + active: true, + created_at: new Date("2024-08-22T06:44:11.012550Z"), + updated_at: new Date("2024-08-22T06:44:11.012559Z"), + workspace: 512 + } + ] + }; + + export const mockAccountsPayable: PaginatedDestinationAttribute = { + count: 3, + next: null, + previous: null, + results: [ + { + id: 253180, + attribute_type: 'ACCOUNTS_PAYABLE', + display_name: 'Accounts Payable', + value: 'Accounts Payable (A/P)', + destination_id: '33', + active: true, + created_at: new Date("2024-08-22T06:44:11.122791Z"), + updated_at: new Date("2024-08-22T06:44:11.122810Z"), + workspace: 512 + }, + { + id: 253181, + attribute_type: 'ACCOUNTS_PAYABLE', + display_name: 'Accounts Payable', + value: 'Accounts Payable (A/P) 2', + destination_id: '91', + active: true, + created_at: new Date("2024-08-22T06:44:11.122836Z"), + updated_at: new Date("2024-08-22T06:44:11.122842Z"), + workspace: 512 + }, + { + id: 253182, + attribute_type: 'ACCOUNTS_PAYABLE', + display_name: 'Accounts Payable', + value: 'Employee Reimbursables', + destination_id: '129', + active: true, + created_at: new Date("2024-08-22T06:44:11.122862Z"), + updated_at: new Date("2024-08-22T06:44:11.122867Z"), + workspace: 512 + } + ] + }; + + export const mockVendors: PaginatedDestinationAttribute = { + count: 182, + next: "http://quickbooks-api.staging-integrations:8000/api/workspaces/512/mappings/paginated_destination_attributes/?active=true&attribute_type=VENDOR&limit=100&offset=100", + previous: null, + results: [ + { + id: 253195, + attribute_type: 'VENDOR', + display_name: 'vendor', + value: '1', + destination_id: '215', + active: true, + created_at: new Date("2024-08-22T06:44:16.926440Z"), + updated_at: new Date("2024-08-22T06:44:16.926468Z"), + workspace: 512 + }, + { + id: 253196, + attribute_type: 'VENDOR', + display_name: 'vendor', + value: 'AMAZON MKTPLACE', + destination_id: '159', + active: true, + created_at: new Date("2024-08-22T06:44:16.926507Z"), + updated_at: new Date("2024-08-22T06:44:16.926517Z"), + workspace: 512 + }, + { + id: 253294, + attribute_type: 'VENDOR', + display_name: 'vendor', + value: 'Mahoney Mugs', + destination_id: '43', + active: true, + created_at: new Date("2024-08-22T06:44:16.992351Z"), + updated_at: new Date("2024-08-22T06:44:16.992356Z"), + workspace: 512 + } + ] + }; + + export const mockExportSettingsResponse: any = { + workspace_general_settings: { + reimbursable_expenses_object: QBOReimbursableExpensesObject.BILL, + corporate_credit_card_expenses_object: QBOCorporateCreditCardExpensesObject.BILL, + is_simplify_report_closure_enabled: true, + name_in_journal_entry: NameInJournalEntry.EMPLOYEE + }, + expense_group_settings: { + reimbursable_expense_group_fields: [ + 'fund_source', + 'expense_id', + 'employee_email', + 'spent_at' + ], + corporate_credit_card_expense_group_fields: [ + 'claim_number', + 'fund_source', + 'employee_email', + 'verified_at', + 'report_id' + ], + expense_state: ExpenseState.PAYMENT_PROCESSING, + ccc_expense_state: CCCExpenseState.APPROVED, + reimbursable_export_date_type: ExportDateType.SPENT_AT, + ccc_export_date_type: ExportDateType.VERIFIED_AT, + split_expense_grouping: SplitExpenseGrouping.MULTIPLE_LINE_ITEM + }, + general_mappings: { + accounts_payable: { + name: 'Accounts Payable (A/P) 2', + id: '91' + }, + qbo_expense_account: { + name: null, + id: null + }, + bank_account: { + name: 'Checking Debit Card', + id: '131' + }, + default_ccc_account: { + name: null, + id: null + }, + default_debit_card_account: { + name: null, + id: null + }, + default_ccc_vendor: { + name: 'Ashwin from NetSuite', + id: '110' + } + }, + workspace_id: 512 + }; + + export const mockSaveResponse = { + "workspace_general_settings": { + "reimbursable_expenses_object": "BILL", + "corporate_credit_card_expenses_object": "BILL", + "is_simplify_report_closure_enabled": true, + "name_in_journal_entry": "EMPLOYEE" + }, + "expense_group_settings": { + "reimbursable_expense_group_fields": [ + "employee_email", + "fund_source", + "expense_id", + "spent_at" + ], + "corporate_credit_card_expense_group_fields": [ + "claim_number", + "employee_email", + "fund_source", + "verified_at", + "report_id" + ], + "expense_state": "PAYMENT_PROCESSING", + "ccc_expense_state": "APPROVED", + "reimbursable_export_date_type": "spent_at", + "ccc_export_date_type": "verified_at", + "split_expense_grouping": "MULTIPLE_LINE_ITEM" + }, + "general_mappings": { + "accounts_payable": { + "name": "Accounts Payable (A/P) 2", + "id": "91" + }, + "qbo_expense_account": { + "name": null, + "id": null + }, + "bank_account": { + "name": "Checking Debit Card", + "id": "131" + }, + "default_ccc_account": { + "name": null, + "id": null + }, + "default_debit_card_account": { + "name": null, + "id": null + }, + "default_ccc_vendor": { + "name": "Ashwin from NetSuite", + "id": "110" + } + }, + "workspace_id": 512 +}; + +export const mockExportSettingOptionSearch: ExportSettingOptionSearch = { + searchTerm: 'anish', + destinationOptionKey: QboExportSettingDestinationOptionKey.BANK_ACCOUNT, + destinationAttributes: [] +}; + +export const mockExpenseAccountEvent: ExportSettingOptionSearch = { + searchTerm: 'anish', + destinationOptionKey: QboExportSettingDestinationOptionKey.EXPENSE_ACCOUNT, + destinationAttributes: [] +}; + +export const mockGeneralEvent: ExportSettingOptionSearch = { + searchTerm: 'anish', + destinationOptionKey: QboExportSettingDestinationOptionKey.BANK_ACCOUNT, + destinationAttributes: [] +}; + +// ... (existing imports and fixtures) + +export const mockBrandingConfig: any = { + featureFlags: { + mapEmployees: false, + cloneSettings: false, + exportSettings: { + reimbursableExpenses: false, + nameInJournalEntry: false, + useMerchantInJournalLine: false, + splitExpenseGrouping: false + }, + importSettings: { + tax: false, + importVendorsAsMerchants: false, + importNetsuiteEmployees: false, + importItems: false, + importProjects: false, + allowCustomSegment: false, + dependentField: false, + allowImportCode: false + }, + advancedSettings: { + autoCreateVendors: false, + paymentsSync: false, + singleCreditLineJE: false, + emailNotification: false, + skipExport: false, + defaultFields: false, + autoCreateContacts: false, + useEmployeeAttributes: false, + autoCreateMerchants: false + }, + exportLog: { + expenseType: false + }, + mappings: { + employeeMapping: false + }, + dashboard: { + disconnectButton: false + } + }, + illustrationsAllowed: false, + isGradientAllowed: false, + isIconsInsideButtonAllowed: false, + exposeC1Apps: false, + isBackgroundColorAllowed: false, + isAsterikAllowed: false, + allowIntacctHelperDoc: false +}; + +export const mockExportSettingFormValueforNavigate = { + workspaceGeneralSettings: { + reimbursableExpensesObject: 'BILL', + corporateCreditCardExpensesObject: 'CREDIT_CARD_PURCHASE', + isSimplifyReportClosureEnabled: true, + nameInJournalEntry: 'EMPLOYEE' + }, + expenseGroupSettings: { + expenseState: 'PAYMENT_PROCESSING', + reimbursableExpenseGroupFields: ['EXPENSE_ID'], + corporateCreditCardExpenseGroupFields: ['EXPENSE_ID'], + splitExpenseGrouping: SplitExpenseGrouping.MULTIPLE_LINE_ITEM + }, + generalMappings: { + bankAccount: { id: '1', name: 'Bank Account' }, + accountsPayable: { id: '2', name: 'Accounts Payable' }, + defaultCCCAccount: { id: '3', name: 'Credit Card Account' }, + defaultVendor: { id: '4', name: 'Default Vendor' } + } +}; + +// Set up the mock data +export const mockDateOptionsforWatchers = [ + { label: 'Current Date', value: 'current_date' }, + { label: 'Verification date', value: 'verified_at' }, + { label: 'Spend date', value: 'spent_at' }, + { label: 'Approval date', value: 'approved_at' }, + { label: 'Last Spend date', value: 'last_spent_at' } +]; \ No newline at end of file