Files
cbsnexora/src/app/accounting/search-journal-entry/search-journal-entry.component.ts
T
Felix van Hove 7fd1486c08 Upgrading to Angular 20:
Replaced checked in package-lock.json by regenerated one.
Ran ng update @angular/core@20 @angular/cli@20
Ran npm install @angular-builders/jest@^20.0.0 @fortawesome/angular-fontawesome@^3.0.0 @swimlane/ngx-graph@^11
Ran npm install ngx-mat-select-search@^8.0.2.
Ran ng update @angular/material@20.
Updated all eslint and typescript to the latest versions.
Removed @angular-devkit/schematics.
Updated fontawesome.
Updated rxjs and tslib.
Moved required node version from 16 to 20.
Updated jest-preset-angular.
Set preserveWhitespaces to false.
Corrected two type declarations in two typescript files to make ng serve run without errors.
Removed unused angular-devkit/schematics from view-user.component.ts .
Updated list of supported browsers.
Upgraded prettier.
Ran prettier . --write the first time.
Ran prettier . --write the second time.
Ran ng generate @angular/core:inject with defaults, excepted a handful of files.
Fixed remaining eslint problems.
Added dist directory to ignores in eslint.config.js.
Let prettier fix formatting, almost everything is just line breaks.
Made htmlhint ignore @if and @for Angular constructs.
Updated node version in Github workflow to 22.12.0 (build.yml).
Updated .env.ts.
Updated Angular datetime format strings from YYYY-MM-DD to yyyy-MM-dd.
Merged changes from dev into angular20 branch.
2025-12-08 11:01:01 +01:00

396 lines
11 KiB
TypeScript

/** Angular Imports */
import { Component, OnInit, ViewChild, AfterViewInit, inject } from '@angular/core';
import { MatPaginator } from '@angular/material/paginator';
import { MatSort, MatSortHeader } from '@angular/material/sort';
import { UntypedFormControl, ReactiveFormsModule } from '@angular/forms';
import { ActivatedRoute, RouterLink } from '@angular/router';
/** rxjs Imports */
import { merge } from 'rxjs';
import { tap, startWith, map, distinctUntilChanged, debounceTime } from 'rxjs/operators';
/** Custom Services */
import { AccountingService } from '../accounting.service';
import { SettingsService } from 'app/settings/settings.service';
/** Custom Data Source */
import { JournalEntriesDataSource } from './journal-entry.datasource';
import { Dates } from 'app/core/utils/dates';
import { MatAutocompleteTrigger, MatOption, MatAutocomplete } from '@angular/material/autocomplete';
import { GlAccountSelectorComponent } from '../../shared/accounting/gl-account-selector/gl-account-selector.component';
import { AsyncPipe } from '@angular/common';
import {
MatTable,
MatColumnDef,
MatHeaderCellDef,
MatHeaderCell,
MatCellDef,
MatCell,
MatHeaderRowDef,
MatHeaderRow,
MatRowDef,
MatRow
} from '@angular/material/table';
import { DateFormatPipe } from '../../pipes/date-format.pipe';
import { DatetimeFormatPipe } from '../../pipes/datetime-format.pipe';
import { FormatNumberPipe } from '../../pipes/format-number.pipe';
import { STANDALONE_SHARED_IMPORTS } from 'app/standalone-shared.module';
/**
* Search journal entry component.
*/
@Component({
selector: 'mifosx-search-journal-entry',
templateUrl: './search-journal-entry.component.html',
styleUrls: ['./search-journal-entry.component.scss'],
imports: [
...STANDALONE_SHARED_IMPORTS,
MatAutocompleteTrigger,
GlAccountSelectorComponent,
MatAutocomplete,
MatTable,
MatSort,
MatColumnDef,
MatHeaderCellDef,
MatHeaderCell,
MatSortHeader,
MatCellDef,
MatCell,
MatHeaderRowDef,
MatHeaderRow,
MatRowDef,
MatRow,
MatPaginator,
AsyncPipe,
DateFormatPipe,
DatetimeFormatPipe,
FormatNumberPipe
]
})
export class SearchJournalEntryComponent implements OnInit, AfterViewInit {
private accountingService = inject(AccountingService);
private settingsService = inject(SettingsService);
private dateUtils = inject(Dates);
private route = inject(ActivatedRoute);
/** Minimum transaction date allowed. */
minDate = new Date(2000, 0, 1);
/** Maximum transaction date allowed. */
maxDate = new Date();
/** Office name filter form control. */
officeName = new UntypedFormControl();
/** Office data. */
officeData: any;
/** Filtered office data for autocomplete. */
filteredOfficeData: any;
/** Gl Account filter form control. */
glAccount = new UntypedFormControl();
/** Gl Account data. */
glAccountData: any;
/** Filtered gl account data. */
filteredGLAccountData: any;
/** Entry type filter form control. */
entryTypeFilter = new UntypedFormControl('');
/** Entry type filter data. */
entryTypeFilterData = [
{
option: 'All',
value: ''
},
{
option: 'Manual Entries',
value: true
},
{
option: 'System Entries',
value: false // Bug: unable to implement from server side
}
];
/** Transaction date from form control. */
transactionDateFrom = new UntypedFormControl(new Date(new Date().setMonth(new Date().getMonth() - 1)));
/** Transaction date to form control. */
transactionDateTo = new UntypedFormControl(new Date());
/** Transaction ID form control. */
transactionId = new UntypedFormControl();
/** Submitted on date from form control. */
submittedOnDateFrom = new UntypedFormControl();
/** Submitted on date to form control. */
submittedOnDateTo = new UntypedFormControl();
/** Columns to be displayed in journal entries table. */
displayedColumns: string[] = [
'id',
'officeName',
'transactionId',
'transactionDate',
'glAccountType',
'createdByUserName',
'submittedOnDate',
'glAccountCode',
'glAccountName',
'currency',
'debit',
'credit'
];
/** Data source for journal entries table. */
dataSource: JournalEntriesDataSource;
/** Journal entries filter. */
filterJournalEntriesBy = [
{
type: 'officeId',
value: ''
},
{
type: 'glAccountId',
value: ''
},
{
type: 'manualEntriesOnly',
value: ''
},
{
type: 'transactionId',
value: ''
},
{
type: 'fromDate',
value: this.dateUtils.formatDate(
new Date(new Date().setMonth(new Date().getMonth() - 1)),
this.settingsService.dateFormat
)
},
{
type: 'toDate',
value: this.dateUtils.formatDate(new Date(), this.settingsService.dateFormat)
},
{
type: 'submittedOnDateFrom',
value: ''
},
{
type: 'submittedOnDateTo',
value: ''
},
{
type: 'dateFormat',
value: this.settingsService.dateFormat
},
{
type: 'locale',
value: this.settingsService.language.code
}
];
/** Paginator for journal entries table. */
@ViewChild(MatPaginator, { static: true }) paginator: MatPaginator;
/** Sorter for journal entries table. */
@ViewChild(MatSort, { static: true }) sort: MatSort;
/**
* Retrieves the offices and gl accounts data from `resolve`.
* @param {AccountingService} accountingService Accounting Service.
* @param {ActivatedRoute} route Activated Route.
* @param {SettingsService} settingsService Settings Service.
*/
constructor() {
this.route.data.subscribe((data: { offices: any; glAccounts: any }) => {
this.officeData = data.offices;
this.glAccountData = data.glAccounts;
});
}
/**
* Sets filtered offices and gl accounts for autocomplete and journal entries table.
*/
ngOnInit() {
this.maxDate = this.settingsService.businessDate;
this.setFilteredOffices();
this.setFilteredGlAccounts();
this.getJournalEntries();
}
/**
* Subscribes to all search filters:
* Office Name, GL Account, Transaction ID, Transaction Date From, Transaction Date To,
* sort change and page change.
*/
ngAfterViewInit() {
this.officeName.valueChanges
.pipe(
map((value) => (value.id ? value.id : '')),
debounceTime(500),
distinctUntilChanged(),
tap((filterValue) => {
this.applyFilter(filterValue, 'officeId');
})
)
.subscribe();
this.glAccount.valueChanges
.pipe(
map((value) => (value ? value : '')),
debounceTime(500),
distinctUntilChanged(),
tap((filterValue) => {
this.applyFilter(filterValue, 'glAccountId');
})
)
.subscribe();
this.transactionId.valueChanges
.pipe(
debounceTime(500),
distinctUntilChanged(),
tap((filterValue) => {
this.applyFilter(filterValue, 'transactionId');
})
)
.subscribe();
this.transactionDateFrom.valueChanges
.pipe(
debounceTime(500),
distinctUntilChanged(),
tap((filterValue) => {
this.applyFilter(this.dateUtils.formatDate(filterValue, this.settingsService.dateFormat), 'fromDate');
})
)
.subscribe();
this.transactionDateTo.valueChanges
.pipe(
debounceTime(500),
distinctUntilChanged(),
tap((filterValue) => {
this.applyFilter(this.dateUtils.formatDate(filterValue, this.settingsService.dateFormat), 'toDate');
})
)
.subscribe();
this.submittedOnDateFrom.valueChanges
.pipe(
debounceTime(500),
distinctUntilChanged(),
tap((filterValue) => {
this.applyFilter(
this.dateUtils.formatDate(filterValue, this.settingsService.dateFormat),
'submittedOnDateFrom'
);
})
)
.subscribe();
this.submittedOnDateTo.valueChanges
.pipe(
debounceTime(500),
distinctUntilChanged(),
tap((filterValue) => {
this.applyFilter(
this.dateUtils.formatDate(filterValue, this.settingsService.dateFormat),
'submittedOnDateTo'
);
})
)
.subscribe();
this.sort.sortChange.subscribe(() => (this.paginator.pageIndex = 0));
merge(this.sort.sortChange, this.paginator.page)
.pipe(tap(() => this.loadJournalEntriesPage()))
.subscribe();
}
/**
* Loads a page of journal entries.
*/
loadJournalEntriesPage() {
if (!this.sort.direction) {
delete this.sort.active;
}
this.dataSource.getJournalEntries(
this.filterJournalEntriesBy,
this.sort.active,
this.sort.direction,
this.paginator.pageIndex,
this.paginator.pageSize
);
}
/**
* Filters data in journal entries table based on passed value and poperty.
* @param {string} filterValue Value to filter data.
* @param {string} property Property to filter data by.
*/
applyFilter(filterValue: string, property: string) {
this.paginator.pageIndex = 0;
const findIndex = this.filterJournalEntriesBy.findIndex((filter) => filter.type === property);
this.filterJournalEntriesBy[findIndex].value = filterValue;
this.loadJournalEntriesPage();
}
/**
* Displays office name in form control input.
* @param {any} office Office data.
* @returns {string} Office name if valid otherwise undefined.
*/
displayOfficeName(office?: any): string | undefined {
return office ? office.name : undefined;
}
/**
* Sets filtered offices for autocomplete.
*/
setFilteredOffices() {
this.filteredOfficeData = this.officeName.valueChanges.pipe(
startWith(''),
map((office: any) => (typeof office === 'string' ? office : office.name)),
map((officeName: string) => (officeName ? this.filterOfficeAutocompleteData(officeName) : this.officeData))
);
}
/**
* Sets filtered gl accounts for autocomplete.
*/
setFilteredGlAccounts() {
this.filteredGLAccountData = this.glAccount.valueChanges.pipe(
startWith(''),
map((glAccount: any) =>
typeof glAccount === 'string' ? glAccount : glAccount.name + ' (' + glAccount.glCode + ')'
),
map((glAccount: string) => (glAccount ? this.filterGLAccountAutocompleteData(glAccount) : this.glAccountData))
);
}
/**
* Filters offices.
* @param {string} officeName Office name to filter office by.
* @returns {any} Filtered offices.
*/
private filterOfficeAutocompleteData(officeName: string): any {
return this.officeData.filter((office: any) => office.name.toLowerCase().includes(officeName.toLowerCase()));
}
/**
* Filters gl accounts.
* @param {string} glAccount Gl Account name to filter gl account by.
* @returns {any} Filtered gl accounts.
*/
private filterGLAccountAutocompleteData(glAccount: string): any {
return this.glAccountData.filter((option: any) =>
(option.name + ' (' + option.glCode + ')').toLowerCase().includes(glAccount.toLowerCase())
);
}
/**
* Initializes the data source for journal entries table and loads the first page.
*/
getJournalEntries() {
this.dataSource = new JournalEntriesDataSource(this.accountingService);
this.dataSource.getJournalEntries(
this.filterJournalEntriesBy,
this.sort.active,
this.sort.direction,
this.paginator.pageIndex,
this.paginator.pageSize
);
}
}