From cd02aebacc8b5f34e0b93788fbb5cd96ba9165e6 Mon Sep 17 00:00:00 2001 From: Galen Charlton Date: Sun, 28 Mar 2021 14:05:09 -0400 Subject: [PATCH] funds: finish funding sources tab Signed-off-by: Galen Charlton --- ...nding-source-transactions-dialog.component.html | 80 ++++++++ ...funding-source-transactions-dialog.component.ts | 168 ++++++++++++++++ .../admin/acq/funds/funding-sources.component.html | 111 +++++++++++ .../admin/acq/funds/funding-sources.component.ts | 221 +++++++++++++++++++++ .../app/staff/admin/acq/funds/funds.component.html | 2 +- .../src/app/staff/admin/acq/funds/funds.module.ts | 6 +- 6 files changed, 586 insertions(+), 2 deletions(-) create mode 100644 Open-ILS/src/eg2/src/app/staff/admin/acq/funds/funding-source-transactions-dialog.component.html create mode 100644 Open-ILS/src/eg2/src/app/staff/admin/acq/funds/funding-source-transactions-dialog.component.ts create mode 100644 Open-ILS/src/eg2/src/app/staff/admin/acq/funds/funding-sources.component.html create mode 100644 Open-ILS/src/eg2/src/app/staff/admin/acq/funds/funding-sources.component.ts diff --git a/Open-ILS/src/eg2/src/app/staff/admin/acq/funds/funding-source-transactions-dialog.component.html b/Open-ILS/src/eg2/src/app/staff/admin/acq/funds/funding-source-transactions-dialog.component.html new file mode 100644 index 0000000000..a48042ce16 --- /dev/null +++ b/Open-ILS/src/eg2/src/app/staff/admin/acq/funds/funding-source-transactions-dialog.component.html @@ -0,0 +1,80 @@ + + + + +
+ +
+
+ + {{value}} + +
+
+ + + + + + + + + + diff --git a/Open-ILS/src/eg2/src/app/staff/admin/acq/funds/funding-source-transactions-dialog.component.ts b/Open-ILS/src/eg2/src/app/staff/admin/acq/funds/funding-source-transactions-dialog.component.ts new file mode 100644 index 0000000000..bce49946ac --- /dev/null +++ b/Open-ILS/src/eg2/src/app/staff/admin/acq/funds/funding-source-transactions-dialog.component.ts @@ -0,0 +1,168 @@ +import {Component, Input, ViewChild, TemplateRef, OnInit} from '@angular/core'; +import {DialogComponent} from '@eg/share/dialog/dialog.component'; +import {IdlService, IdlObject} from '@eg/core/idl.service'; +import {FormatService} from '@eg/core/format.service'; +import {EventService} from '@eg/core/event.service'; +import {NetService} from '@eg/core/net.service'; +import {AuthService} from '@eg/core/auth.service'; +import {PcrudService} from '@eg/core/pcrud.service'; +import {FmRecordEditorComponent} from '@eg/share/fm-editor/fm-editor.component'; +import {GridDataSource, GridCellTextGenerator} from '@eg/share/grid/grid'; +import {Pager} from '@eg/share/util/pager'; +import {NgbModal} from '@ng-bootstrap/ng-bootstrap'; +import {StringComponent} from '@eg/share/string/string.component'; +import {ToastService} from '@eg/share/toast/toast.service'; +import {GridComponent} from '@eg/share/grid/grid.component'; +import {OrgService} from '@eg/core/org.service'; + +@Component({ + selector: 'eg-funding-source-transactions-dialog', + templateUrl: './funding-source-transactions-dialog.component.html' +}) + +export class FundingSourceTransactionsDialogComponent + extends DialogComponent implements OnInit { + + @Input() fundingSourceId: number; + @Input() activeTab = 'credits'; + fundingSource: IdlObject; + idlDef: any; + fieldOrder: any; + acqfaDataSource: GridDataSource + acqfscredDataSource: GridDataSource + cellTextGenerator: GridCellTextGenerator; + + @ViewChild('applyCreditDialog', { static: true }) applyCreditDialog: FmRecordEditorComponent; + @ViewChild('allocateToFundDialog', { static: true }) allocateToFundDialog: FmRecordEditorComponent; + @ViewChild('successString', { static: true }) successString: StringComponent; + @ViewChild('updateFailedString', { static: false }) updateFailedString: StringComponent; + + constructor( + private idl: IdlService, + private evt: EventService, + private net: NetService, + private auth: AuthService, + private pcrud: PcrudService, + private org: OrgService, + private format: FormatService, + private toast: ToastService, + private modal: NgbModal + ) { + super(modal); + } + + ngOnInit() { + this.cellTextGenerator = { + fund: row => { + return row.code() + ' (' + row.year() + ') (' + + this.getOrgShortname(row.org()) + ')'; + } + }; + this.fundingSource = null; + this.onOpen$.subscribe(() => this._initRecord()); + this.idlDef = this.idl.classes['acqfs'] + this.fieldOrder = 'name,code,year,org,active,currency_type,balance_stop_percentage,balance_warning_percentage,propagate,rollover'; + } + + private _initRecord() { + this.acqfaDataSource = this._getDataSource('acqfa', 'create_time DESC'); + this.acqfscredDataSource = this._getDataSource('acqfscred', 'effective_date DESC'); + this.pcrud.retrieve('acqfs', this.fundingSourceId, {} + ).subscribe(res => this.fundingSource = res); + } + + _getDataSource(idlClass: string, sortField: string): GridDataSource { + const gridSource = new GridDataSource(); + + gridSource.getRows = (pager: Pager, sort: any[]) => { + const orderBy: any = {}; + if (sort.length) { + // Sort specified from grid + orderBy[idlClass] = sort[0].name + ' ' + sort[0].dir; + } else if (sortField) { + // Default sort field + orderBy[idlClass] = sortField; + } + + const searchOps = { + offset: pager.offset, + limit: pager.limit, + order_by: orderBy, + }; + const reqOps = { + fleshSelectors: true, + }; + + const search: any = new Array(); + search.push({ funding_source: this.fundingSourceId }); + + Object.keys(gridSource.filters).forEach(key => { + Object.keys(gridSource.filters[key]).forEach(key2 => { + search.push(gridSource.filters[key][key2]); + }); + }); + + return this.pcrud.search( + idlClass, search, searchOps, reqOps); + }; + + return gridSource; + } + + formatCurrency(value: any) { + return this.format.transform({ + value: value, + datatype: 'money' + }); + } + + createCredit(grid: GridComponent) { + const credit = this.idl.create('acqfscred'); + credit.funding_source(this.fundingSourceId); + this.applyCreditDialog.defaultNewRecord = credit; + this.applyCreditDialog.mode = 'create'; + this.applyCreditDialog.hiddenFieldsList = ['id', 'funding_source']; + this.applyCreditDialog.fieldOrder = 'amount,note,effective_date,deadline_date'; + this.applyCreditDialog.open().subscribe( + result => { + this.successString.current() + .then(str => this.toast.success(str)); + grid.reload(); + }, + error => { + this.updateFailedString.current() + .then(str => this.toast.danger(str)); + } + ); + } + + allocateToFund(grid: GridComponent) { + const allocation = this.idl.create('acqfa'); + allocation.funding_source(this.fundingSourceId); + allocation.allocator(this.auth.user().id()); + this.allocateToFundDialog.defaultNewRecord = allocation; + this.allocateToFundDialog.mode = 'create'; + + this.allocateToFundDialog.hiddenFieldsList = ['id', 'funding_source', 'allocator', 'create_time']; + this.allocateToFundDialog.fieldOrder = 'fund,amount,note'; + this.allocateToFundDialog.open().subscribe( + result => { + this.successString.current() + .then(str => this.toast.success(str)); + grid.reload(); + }, + error => { + this.updateFailedString.current() + .then(str => this.toast.danger(str)); + } + ); + } + + getOrgShortname(ou: any) { + if (typeof ou === 'object') { + return ou.shortname(); + } else { + return this.org.get(ou).shortname(); + } + } +} diff --git a/Open-ILS/src/eg2/src/app/staff/admin/acq/funds/funding-sources.component.html b/Open-ILS/src/eg2/src/app/staff/admin/acq/funds/funding-sources.component.html new file mode 100644 index 0000000000..76833a8617 --- /dev/null +++ b/Open-ILS/src/eg2/src/app/staff/admin/acq/funds/funding-sources.component.html @@ -0,0 +1,111 @@ +{{idlClassDef.label}} Update Succeeded + + +Update of {{idlClassDef.label}} failed + + +Delete of {{idlClassDef.label}} failed or was not allowed + + +{{idlClassDef.label}} Successfully Deleted + + +{{idlClassDef.label}} Successfully Created + + +Failed to create new {{idlClassDef.label}} + + + +
+
+ + + + +
+
+
+
+ + + + + + + + + + {{configLinkLabel(row, col)}} + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Open-ILS/src/eg2/src/app/staff/admin/acq/funds/funding-sources.component.ts b/Open-ILS/src/eg2/src/app/staff/admin/acq/funds/funding-sources.component.ts new file mode 100644 index 0000000000..4d31a4132d --- /dev/null +++ b/Open-ILS/src/eg2/src/app/staff/admin/acq/funds/funding-sources.component.ts @@ -0,0 +1,221 @@ +import {Component, Input, ViewChild, OnInit} from '@angular/core'; +import {Location} from '@angular/common'; +import {FormatService} from '@eg/core/format.service'; +import {GridDataSource, GridCellTextGenerator} from '@eg/share/grid/grid'; +import {GridComponent} from '@eg/share/grid/grid.component'; +import {AdminPageComponent} from '@eg/staff/share/admin-page/admin-page.component'; +import {Pager} from '@eg/share/util/pager'; +import {ActivatedRoute} from '@angular/router'; +import {IdlService, IdlObject} from '@eg/core/idl.service'; +import {ToastService} from '@eg/share/toast/toast.service'; +import {PcrudService} from '@eg/core/pcrud.service'; +import {FmRecordEditorComponent} from '@eg/share/fm-editor/fm-editor.component'; +import {OrgService} from '@eg/core/org.service'; +import {PermService} from '@eg/core/perm.service'; +import {AuthService} from '@eg/core/auth.service'; +import {NetService} from '@eg/core/net.service'; +import {map, mergeMap} from 'rxjs/operators'; +import {StringComponent} from '@eg/share/string/string.component'; +import {Observable, forkJoin, of} from 'rxjs'; +import {AlertDialogComponent} from '@eg/share/dialog/alert.component'; +import {ConfirmDialogComponent} from '@eg/share/dialog/confirm.component'; +import {FundingSourceTransactionsDialogComponent} from './funding-source-transactions-dialog.component'; + +@Component({ + selector: 'eg-funding-sources', + templateUrl: './funding-sources.component.html' +}) + +export class FundingSourcesComponent extends AdminPageComponent implements OnInit { + idlClass = 'acqfs'; + classLabel: string; + + @ViewChild('grid', { static: true }) grid: GridComponent; + @ViewChild('fundingSourceTransactionsDialog', { static: false }) fundingSourceTransactionsDialog: FundingSourceTransactionsDialogComponent; + @ViewChild('applyCreditDialog', { static: true }) applyCreditDialog: FmRecordEditorComponent; + @ViewChild('allocateToFundDialog', { static: true }) allocateToFundDialog: FmRecordEditorComponent; + @ViewChild('alertDialog', {static: false}) private alertDialog: AlertDialogComponent; + @ViewChild('confirmDel', { static: true }) confirmDel: ConfirmDialogComponent; + + cellTextGenerator: GridCellTextGenerator; + notOneSelectedRow: (rows: IdlObject[]) => boolean; + + constructor( + route: ActivatedRoute, + ngLocation: Location, + format: FormatService, + idl: IdlService, + org: OrgService, + auth: AuthService, + pcrud: PcrudService, + perm: PermService, + toast: ToastService, + private net: NetService + ) { + super(route, ngLocation, format, idl, org, auth, pcrud, perm, toast); + this.dataSource = new GridDataSource(); + } + + ngOnInit() { + this.cellTextGenerator = { + name: row => row.name() + }; + this.notOneSelectedRow = (rows: IdlObject[]) => (rows.length !== 1); + this.fieldOrder = 'name,code,year,org,active,currency_type,balance_stop_percentage,balance_warning_percentage,propagate,rollover'; + this.defaultNewRecord = this.idl.create('acqfs'); + this.defaultNewRecord.owner(this.auth.user().ws_ou()); + + this.dataSource.getRows = (pager: Pager, sort: any[]) => { + const orderBy: any = {}; + if (sort.length) { + // Sort specified from grid + orderBy[this.idlClass] = sort[0].name + ' ' + sort[0].dir; + } else if (this.sortField) { + // Default sort field + orderBy[this.idlClass] = this.sortField; + } + + const searchOps = { + offset: pager.offset, + limit: pager.limit, + order_by: orderBy, + flesh: 1, + flesh_fields: { + acqfs: ['credits','allocations'] + } + }; + const reqOps = { + fleshSelectors: true, + }; + + if (!this.contextOrg && !Object.keys(this.dataSource.filters).length) { + // No org filter -- fetch all rows + return this.pcrud.retrieveAll( + this.idlClass, searchOps, reqOps) + .pipe(mergeMap((row) => this.calculateSummary(row))); + } + + const search: any = new Array(); + const orgFilter: any = {}; + + if (this.orgField && (this.searchOrgs || this.contextOrg)) { + orgFilter[this.orgField] = + this.searchOrgs.orgIds || [this.contextOrg.id()]; + search.push(orgFilter); + } + + Object.keys(this.dataSource.filters).forEach(key => { + Object.keys(this.dataSource.filters[key]).forEach(key2 => { + search.push(this.dataSource.filters[key][key2]); + }); + }); + + return this.pcrud.search( + this.idlClass, search, searchOps, reqOps) + .pipe(mergeMap((row) => this.calculateSummary(row))); + }; + + super.ngOnInit(); + + this.classLabel = this.idlClassDef.label; + this.includeOrgDescendants = true; + } + + calculateSummary(row: IdlObject): Observable { + row['balance'] = 0; + row['total_credits'] = 0; + row['total_allocations'] = 0; + + row.credits().forEach((c) => row['total_credits'] += Number(c.amount())); + row.allocations().forEach((a) => row['total_allocations'] += Number(a.amount())); + row['balance'] = row['total_credits'] - row['total_allocations']; + return of(row); + } + + deleteSelected(rows: IdlObject[]) { + if (rows.length > 0) { + const id = rows[0].id(); + let can: boolean = true; + forkJoin([ + this.pcrud.search('acqfa', { funding_source: id }, { limit: 1 }, { atomic: true }), + this.pcrud.search('acqfscred', { funding_source: id }, { limit: 1 }, { atomic: true }), + ]).subscribe( + results => { + results.forEach((res) => { + if (res.length > 0) { + can = false; + } + }); + }, + err => {}, + () => { + if (can) { + this.confirmDel.open().subscribe(confirmed => { + if (!confirmed) { return; } + super.deleteSelected([ rows[0] ]); + }); + } else { + this.alertDialog.open(); + } + } + ); + } + } + + openTransactionsDialog(rows: IdlObject[], tab: string) { + if (rows.length !== 1) { return; } + this.fundingSourceTransactionsDialog.fundingSourceId = rows[0].id(); + this.fundingSourceTransactionsDialog.activeTab = tab; + this.fundingSourceTransactionsDialog.open({size: 'xl'}).subscribe( + res => {}, + err => {}, + () => this.grid.reload() + ); + } + + createCredit(rows: IdlObject[]) { + if (rows.length !== 1) { return; } + const fundingSourceId = rows[0].id(); + const credit = this.idl.create('acqfscred'); + credit.funding_source(fundingSourceId); + this.applyCreditDialog.defaultNewRecord = credit; + this.applyCreditDialog.mode = 'create'; + this.applyCreditDialog.hiddenFieldsList = ['id', 'funding_source']; + this.applyCreditDialog.fieldOrder = 'amount,note,effective_date,deadline_date'; + this.applyCreditDialog.open().subscribe( + result => { + this.successString.current() + .then(str => this.toast.success(str)); + this.grid.reload(); + }, + error => { + this.updateFailedString.current() + .then(str => this.toast.danger(str)); + } + ); + } + + allocateToFund(rows: IdlObject[]) { + if (rows.length !== 1) { return; } + const fundingSourceId = rows[0].id(); + const allocation = this.idl.create('acqfa'); + allocation.funding_source(fundingSourceId); + allocation.allocator(this.auth.user().id()); + this.allocateToFundDialog.defaultNewRecord = allocation; + this.allocateToFundDialog.mode = 'create'; + + this.allocateToFundDialog.hiddenFieldsList = ['id', 'funding_source', 'allocator', 'create_time']; + this.allocateToFundDialog.fieldOrder = 'fund,amount,note'; + this.allocateToFundDialog.open().subscribe( + result => { + this.successString.current() + .then(str => this.toast.success(str)); + this.grid.reload(); + }, + error => { + this.updateFailedString.current() + .then(str => this.toast.danger(str)); + } + ); + } +} diff --git a/Open-ILS/src/eg2/src/app/staff/admin/acq/funds/funds.component.html b/Open-ILS/src/eg2/src/app/staff/admin/acq/funds/funds.component.html index c4f3f8f15a..0023e5734b 100644 --- a/Open-ILS/src/eg2/src/app/staff/admin/acq/funds/funds.component.html +++ b/Open-ILS/src/eg2/src/app/staff/admin/acq/funds/funds.component.html @@ -16,7 +16,7 @@ Funding Sources
- +
diff --git a/Open-ILS/src/eg2/src/app/staff/admin/acq/funds/funds.module.ts b/Open-ILS/src/eg2/src/app/staff/admin/acq/funds/funds.module.ts index cf371e0fc4..a74dadd36a 100644 --- a/Open-ILS/src/eg2/src/app/staff/admin/acq/funds/funds.module.ts +++ b/Open-ILS/src/eg2/src/app/staff/admin/acq/funds/funds.module.ts @@ -5,12 +5,16 @@ import {FundsComponent} from './funds.component'; import {FundsRoutingModule} from './routing.module'; import {FundsManagerComponent} from './funds-manager.component'; import {FundDetailsDialogComponent} from './fund-details-dialog.component'; +import {FundingSourcesComponent} from './funding-sources.component'; +import {FundingSourceTransactionsDialogComponent} from './funding-source-transactions-dialog.component'; @NgModule({ declarations: [ FundsComponent, FundsManagerComponent, - FundDetailsDialogComponent + FundDetailsDialogComponent, + FundingSourcesComponent, + FundingSourceTransactionsDialogComponent ], imports: [ StaffCommonModule, -- 2.11.0