Skip to content

Commit

Permalink
feat: add CoA multiselect and update bank account options
Browse files Browse the repository at this point in the history
* feat: add multiselect to choose types of accounts to import

* fix: update default bank a/c options in BC (#1098)

* fix: update default bank a/c options in BC

We now show `ACCOUNT` as well as `BANK_ACCOUNT` attributes as options for default bank accounts.
Only `ACCOUNT`s of category 'Assets' or 'Liabilities' are shown

* fix: after autocomplete search, reset the in progress state of the field
  • Loading branch information
1 parent 2331fcd commit 15d7163
Show file tree
Hide file tree
Showing 9 changed files with 110 additions and 34 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,8 @@ import { IntegrationField } from "../../db/mapping.model";
export type BusinessCentralImportSettings = {
import_settings: {
import_categories: boolean,
import_vendors_as_merchants: boolean
import_vendors_as_merchants: boolean,
charts_of_accounts: string[]
}
mapping_settings: ImportSettingMappingRow[] | []
}
Expand All @@ -25,6 +26,7 @@ export class BusinessCentralImportSettingsModel extends ImportSettingsModel {
const expenseFieldsArray = importSettings?.mapping_settings ? this.constructFormArray(importSettings.mapping_settings, businessCentralFields) : [] ;
return new FormGroup({
importCategories: new FormControl(importSettings?.import_settings?.import_categories ?? false),
chartOfAccountTypes: new FormControl(importSettings?.import_settings.charts_of_accounts ? importSettings?.import_settings.charts_of_accounts : ['Expense']),
importVendorAsMerchant: new FormControl(importSettings?.import_settings?.import_vendors_as_merchants ?? false ),
expenseFields: new FormArray(expenseFieldsArray)
});
Expand All @@ -36,10 +38,15 @@ export class BusinessCentralImportSettingsModel extends ImportSettingsModel {
return {
import_settings: {
import_categories: importSettingsForm.get('importCategories')?.value,
import_vendors_as_merchants: importSettingsForm.get('importVendorAsMerchant')?.value
import_vendors_as_merchants: importSettingsForm.get('importVendorAsMerchant')?.value,
charts_of_accounts: importSettingsForm.get('chartOfAccountTypes')?.value
},
mapping_settings: mappingSettings
};
}

static getChartOfAccountTypesList() {
return ['Expense', 'Assets', 'Income', 'Equity', 'Liabilities', 'Others', 'Cost of Goods Sold'];
}
}

3 changes: 2 additions & 1 deletion src/app/core/models/enum/enum.model.ts
Original file line number Diff line number Diff line change
Expand Up @@ -702,7 +702,8 @@ export enum Sage300ExportSettingDestinationOptionKey {

export enum BCExportSettingDestinationOptionKey {
ACCOUNT = 'ACCOUNT',
VENDOR = 'VENDOR'
VENDOR = 'VENDOR',
REIMBURSABLE_BANK_ACCOUNT = 'REIMBURSABLE_BANK_ACCOUNT'
}

export enum QbdDirectExportSettingDestinationOptionKey {
Expand Down
8 changes: 6 additions & 2 deletions src/app/core/services/common/mapping.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -170,9 +170,9 @@ export class MappingService {
return this.apiService.post(`/workspaces/${this.workspaceService.getWorkspaceId()}/mappings/`, mapping);
}

getPaginatedDestinationAttributes(attributeType: string, value?: string, display_name?: string, appName?: string, detailed_accout_type?: string[]): Observable<PaginatedDestinationAttribute> {
getPaginatedDestinationAttributes(attributeType: string, value?: string, display_name?: string, appName?: string, detailed_accout_type?: string[], categories?: string[]): Observable<PaginatedDestinationAttribute> {
const workspaceId = this.workspaceService.getWorkspaceId();
const params: {limit: number, offset: number, attribute_type: string, active?: boolean, value__icontains?: string, value?: string, display_name__in?: string, detail__account_type__in?: string[]} = {
const params: {limit: number, offset: number, attribute_type: string, active?: boolean, value__icontains?: string, value?: string, display_name__in?: string, detail__account_type__in?: string[], detail__category__in?: string[]} = {
limit: 100,
offset: 0,
attribute_type: attributeType,
Expand All @@ -195,6 +195,10 @@ export class MappingService {
params.detail__account_type__in = detailed_accout_type;
}

if (categories) {
params.detail__category__in = categories;
}

return this.apiService.get(`/workspaces/${workspaceId}/mappings/paginated_destination_attributes/`, params);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -91,8 +91,8 @@
[mandatoryErrorListName]="'Default Bank Account Name'"
[label]="'Set the Default Bank Account as?'"
[subLabel]="'The integration will assign the Expenses that is exported as Journal Entry to the Bank Account selected here.'"
[destinationAttributes]="bankOptions"
[destinationOptionKey]="BCExportSettingDestinationOptionKey.ACCOUNT"
[destinationAttributes]="reimbursableBankOptions"
[destinationOptionKey]="BCExportSettingDestinationOptionKey.REIMBURSABLE_BANK_ACCOUNT"
[isOptionSearchInProgress]="isOptionSearchInProgress"
[isAdvanceSearchEnabled]="true"
(searchOptionsDropdown)="searchOptionsDropdown($event)"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,8 @@ export class BusinessCentralExportSettingsComponent implements OnInit {

bankOptions: DestinationAttribute[];

reimbursableBankOptions: DestinationAttribute[];

vendorOptions: DestinationAttribute[];

isLoading: boolean = true;
Expand Down Expand Up @@ -168,39 +170,63 @@ export class BusinessCentralExportSettingsComponent implements OnInit {
debounceTime(1000)
).subscribe((event: ExportSettingOptionSearch) => {

let existingOptions: DestinationAttribute[];
switch (event.destinationOptionKey) {
case BCExportSettingDestinationOptionKey.ACCOUNT:
existingOptions = this.bankOptions;
break;
case BCExportSettingDestinationOptionKey.VENDOR:
existingOptions = this.vendorOptions;
break;
}

this.mappingService.getPaginatedDestinationAttributes(event.destinationOptionKey, event.searchTerm).subscribe((response) => {

// Insert new options to existing options
response.results.forEach((option) => {
if (!existingOptions.find((existingOption) => existingOption.destination_id === option.destination_id)) {
existingOptions.push(option);
}
if (event.destinationOptionKey === BCExportSettingDestinationOptionKey.REIMBURSABLE_BANK_ACCOUNT) {
const observables = [
this.mappingService.getPaginatedDestinationAttributes('BANK_ACCOUNT', event.searchTerm),
this.mappingService.getPaginatedDestinationAttributes(
'ACCOUNT', event.searchTerm, undefined, undefined, undefined, ['Assets', 'Liabilities']
)
];

forkJoin(observables).subscribe(([bankAccounts, accounts]) => {
// Insert new options (if any) to existing options, and sort them
const newOptions = [...bankAccounts.results, ...accounts.results];
newOptions.forEach((newOption) => {
if (!this.reimbursableBankOptions.find((existingOption) => existingOption.destination_id === newOption.destination_id)) {
this.reimbursableBankOptions.push(newOption);
}
});

this.reimbursableBankOptions.sort((a, b) => (a.value || '').localeCompare(b.value || ''));
this.isOptionSearchInProgress = false;
});

} else {

let existingOptions: DestinationAttribute[];
switch (event.destinationOptionKey) {
case BCExportSettingDestinationOptionKey.ACCOUNT:
this.bankOptions = existingOptions.concat();
this.bankOptions.sort((a, b) => (a.value || '').localeCompare(b.value || ''));
existingOptions = this.bankOptions;
break;
case BCExportSettingDestinationOptionKey.VENDOR:
this.vendorOptions = existingOptions.concat();
this.vendorOptions.sort((a, b) => (a.value || '').localeCompare(b.value || ''));
existingOptions = this.vendorOptions;
break;
}

this.isOptionSearchInProgress = false;
});
this.mappingService.getPaginatedDestinationAttributes(event.destinationOptionKey, event.searchTerm).subscribe((response) => {

// Insert new options to existing options
response.results.forEach((option) => {
if (!existingOptions.find((existingOption) => existingOption.destination_id === option.destination_id)) {
existingOptions.push(option);
}
});


switch (event.destinationOptionKey) {
case BCExportSettingDestinationOptionKey.ACCOUNT:
this.bankOptions = existingOptions.concat();
this.bankOptions.sort((a, b) => (a.value || '').localeCompare(b.value || ''));
break;
case BCExportSettingDestinationOptionKey.VENDOR:
this.vendorOptions = existingOptions.concat();
this.vendorOptions.sort((a, b) => (a.value || '').localeCompare(b.value || ''));
break;
}

this.isOptionSearchInProgress = false;
});
}
});
}

Expand Down Expand Up @@ -240,10 +266,19 @@ export class BusinessCentralExportSettingsComponent implements OnInit {
this.mappingService.getPaginatedDestinationAttributes(destinationAttribute).pipe(filter(response => !!response))
);

// For reimbursable default bank account options
const reimbursableBankAccountAttributes = [
this.mappingService.getPaginatedDestinationAttributes('BANK_ACCOUNT'),
this.mappingService.getPaginatedDestinationAttributes(
'ACCOUNT', undefined, undefined, undefined, undefined, ['Assets', 'Liabilities']
)
];

forkJoin([
this.exportSettingService.getExportSettings().pipe(catchError(() => of(null))),
...groupedAttributes
]).subscribe(([exportSettingsResponse, accounts, vendors]) => {
...groupedAttributes,
...reimbursableBankAccountAttributes
]).subscribe(([exportSettingsResponse, accounts, vendors, reimbursableBankAccounts, reimbursableAccounts]) => {
this.exportSettings = exportSettingsResponse;

if (exportSettingsResponse) {
Expand All @@ -266,6 +301,9 @@ export class BusinessCentralExportSettingsComponent implements OnInit {
this.helper.setExportTypeValidatorsAndWatchers(exportModuleRule, this.exportSettingForm, commonFormFields);
this.bankOptions = accounts.results;
this.vendorOptions = vendors.results;
this.reimbursableBankOptions = [...reimbursableBankAccounts.results, ...reimbursableAccounts.results];
this.reimbursableBankOptions.sort((a, b) => (a.value || '').localeCompare(b.value || ''));

this.setupCustomWatchers();
this.optionSearchWatcher();
this.isLoading = false;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,27 @@
[isSectionHeader]="false"
[iconPath]="'arrow-tail-down'">
</app-configuration-toggle-field>

<div class="tw-pr-24-px tw-pl-64-px tw-py-0 tw-pb-24-px" *ngIf="importSettingForm.get('importCategories')?.value">
<div class="tw-flex tw-justify-between tw-items-center">
<h5 class="lg:tw-w-3/5 md:tw-w-1/2 tw-text-slightly-normal-text-color tw-text-14-px !tw-font-500">
Select the accounts from {{ appName }} to import as categories in {{brandingConfig.brandName}}
<p class="tw-text-text-muted" [ngClass]="{'tw-pt-4-px': brandingConfig.brandId === 'co', 'tw-pt-8-px': brandingConfig.brandId !== 'co'}">
By default expense will be selected. Open the dropdown to select more as per your requirements.
</p>
</h5>
<div class="p-field-checkbox tw-pl-34-px">
<p-multiSelect [optionDisabled]="'Expense'" [placeholder]="'Select Chart of Accounts'" [options]="chartOfAccountTypesList" styleClass="tw-z-2 tw-py-8-px tw-px-12-px" [formControlName]="'chartOfAccountTypes'">
<ng-template let-value pTemplate="selectedItems">
<div class="tw-inline-flex tw-align-items-center" *ngFor="let option of value; let i = index">
<div>{{ option }}<span *ngIf="i !== value?.length-1">,&nbsp;</span></div>
</div>
<div *ngIf="!value || value.length === 0">{{helper.sentenseCaseConversion('Select Chart of Accounts')}}</div>
</ng-template>
</p-multiSelect>
</div>
</div>
</div>
</div>
<div class="tw-rounded-lg tw-border-separator tw-border tw-mt-16-px">
<app-configuration-toggle-field
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,8 @@ export class BusinessCentralImportSettingsComponent implements OnInit {

customFieldOption: ExpenseField[] = [{ attribute_type: 'custom_field', display_name: 'Create a Custom Field', source_placeholder: null, is_dependent: false }];

readonly chartOfAccountTypesList: string[] = BusinessCentralImportSettingsModel.getChartOfAccountTypesList();

readonly brandingConfig = brandingConfig;

readonly supportArticleLink: string = brandingKbArticles.onboardingArticles.BUSINESS_CENTRAL.IMPORT_SETTING;
Expand All @@ -68,7 +70,7 @@ export class BusinessCentralImportSettingsComponent implements OnInit {
private mappingService: MappingService,
private helperService: BusinessCentralHelperService,
@Inject(FormBuilder) private formBuilder: FormBuilder,
private helper: HelperService,
public helper: HelperService,
private toastService: IntegrationsToastService,
private trackingService: TrackingService,
private workspaceService: WorkspaceService
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { BusinessCentralAdvancedSettingsComponent } from './business-central-adv
import { BusinessCentralExportSettingsComponent } from './business-central-export-settings/business-central-export-settings.component';
import { BusinessCentralImportSettingsComponent } from './business-central-import-settings/business-central-import-settings.component';
import { SharedModule } from 'src/app/shared/shared.module';
import { MultiSelectModule } from 'primeng/multiselect';

@NgModule({
declarations: [
Expand All @@ -13,6 +14,7 @@ import { SharedModule } from 'src/app/shared/shared.module';
],
imports: [
CommonModule,
MultiSelectModule,
SharedModule
],
exports: [
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,8 @@ export const importSettingsResponse: BusinessCentralImportSettingsGet = {
"id": 123,
"import_settings": {
"import_categories": true,
"import_vendors_as_merchants": true
"import_vendors_as_merchants": true,
"charts_of_accounts": ['Expense']
},
"mapping_settings": [],
"workspace_id": 343,
Expand Down

0 comments on commit 15d7163

Please sign in to comment.