From: Galen Charlton Date: Fri, 26 Mar 2021 21:58:51 +0000 (-0400) Subject: LP#1904244: Angular EDI attr sets interface X-Git-Url: https://old-git.evergreen-ils.org/?a=commitdiff_plain;h=4dd83266d301972557336f208fd0c3f7b3783af0;p=Evergreen.git LP#1904244: Angular EDI attr sets interface Signed-off-by: Galen Charlton Signed-off-by: Ruth Frasur Signed-off-by: Bill Erickson --- diff --git a/Open-ILS/src/eg2/src/app/staff/admin/acq/admin-acq-splash.component.html b/Open-ILS/src/eg2/src/app/staff/admin/acq/admin-acq-splash.component.html index b2fec57c0d..f5de0e2db3 100644 --- a/Open-ILS/src/eg2/src/app/staff/admin/acq/admin-acq-splash.component.html +++ b/Open-ILS/src/eg2/src/app/staff/admin/acq/admin-acq-splash.component.html @@ -21,10 +21,7 @@ - + + + + diff --git a/Open-ILS/src/eg2/src/app/staff/admin/acq/edi_attr_set/edi-attr-set-edit-dialog.component.ts b/Open-ILS/src/eg2/src/app/staff/admin/acq/edi_attr_set/edi-attr-set-edit-dialog.component.ts new file mode 100644 index 0000000000..203f72600e --- /dev/null +++ b/Open-ILS/src/eg2/src/app/staff/admin/acq/edi_attr_set/edi-attr-set-edit-dialog.component.ts @@ -0,0 +1,151 @@ +import {Component, Input, ViewChild, TemplateRef, OnInit} from '@angular/core'; +import {DialogComponent} from '@eg/share/dialog/dialog.component'; +import {NgForm} from '@angular/forms'; +import {IdlService, IdlObject} from '@eg/core/idl.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 {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 {PermService} from '@eg/core/perm.service'; + +@Component({ + selector: 'eg-edi-attr-set-edit-dialog', + templateUrl: './edi-attr-set-edit-dialog.component.html' +}) + +export class EdiAttrSetEditDialogComponent + extends DialogComponent implements OnInit { + + @Input() mode = 'create'; + @Input() attrSetId: number; + @Input() cloneSource: number; + attrSet: IdlObject; + attrInputs: any = []; + clonedLabel = ''; + + constructor( + private idl: IdlService, + private evt: EventService, + private net: NetService, + private auth: AuthService, + private pcrud: PcrudService, + private perm: PermService, + private toast: ToastService, + private modal: NgbModal + ) { + super(modal); + } + + ngOnInit() { + this.onOpen$.subscribe(() => this._initRecord()); + } + + private _initRecord() { + this.attrSet = null; + this.attrInputs = []; + this.clonedLabel = ''; + if (this.mode === 'update') { + this.pcrud.retrieve('aeas', this.attrSetId, { + flesh: 1, + flesh_fields: { aeas: ['attr_maps'] } + }).subscribe(res => { + this.attrSet = res; + this._generateAttrInputs(); + }); + } else if (this.mode === 'clone') { + this.pcrud.retrieve('aeas', this.cloneSource, { + flesh: 1, + flesh_fields: { aeas: ['attr_maps'] } + }).subscribe(res => { + this.clonedLabel = res.label(); + this.attrSet = this.idl.create('aeas'); + this.attrSet.attr_maps([]); + res.attr_maps().forEach((m) => { + const newMap = this.idl.create('aeasm'); + newMap.attr(m.attr()); + this.attrSet.attr_maps().push(newMap); + }); + this._generateAttrInputs(); + }); + } else if (this.mode === 'create') { + this.attrSet = this.idl.create('aeas'); + this.attrSet.attr_maps([]); + this._generateAttrInputs(); + } + } + + _generateAttrInputs() { + const hasAttr: {[key: string]: boolean} = {}; + const hasAttrId: {[key: string]: number} = {}; + this.attrSet.attr_maps().forEach((m) => { + hasAttr[m.attr()] = true; + hasAttrId[m.attr()] = m.id(); + }); + this.pcrud.retrieveAll('aea', {order_by: {aea: 'key'}}).subscribe(attr => { + const inp = { + key: attr.key(), + label: attr.label(), + id: null, + selected: false + }; + if (attr.key() in hasAttr) { + inp.selected = true; + inp.id = hasAttrId[attr.key()]; + } + this.attrInputs.push(inp); + }); + } + + save() { + if (this.attrSet.id() === undefined || this.attrSet.id() === null) { + this.attrSet.isnew(true); + } else { + this.attrSet.ischanged(true); + } + this.pcrud.autoApply([this.attrSet]).subscribe(res => { + const setId = this.mode === 'update' ? res : res.id(); + const updates: IdlObject[] = []; + if (this.mode === 'create' || this.mode === 'clone') { + this.attrInputs.forEach((inp) => { + if (inp.selected) { + const aesm = this.idl.create('aeasm'); + aesm.attr(inp.key); + aesm.attr_set(setId); + aesm.isnew(true); + updates.push(aesm); + } + }); + } else { + // updating an existing set + this.attrInputs.forEach((inp) => { + if (inp.id) { + if (!inp.selected) { + // used to be wanted, but no longer + const aesm = this.idl.create('aeasm'); + aesm.id(inp.id); + aesm.isdeleted(true); + updates.push(aesm); + } + } else if (inp.selected) { + // no ID, must be newly checked + const aesm = this.idl.create('aeasm'); + aesm.attr(inp.key); + aesm.attr_set(setId); + aesm.isnew(true); + updates.push(aesm); + } + }); + } + this.pcrud.autoApply(updates).subscribe( + ret => this.close(true), + err => this.close(err), + () => this.close(true) + ); + }, err => this.close(false)); + } + +} diff --git a/Open-ILS/src/eg2/src/app/staff/admin/acq/edi_attr_set/edi-attr-set-providers-dialog.component.html b/Open-ILS/src/eg2/src/app/staff/admin/acq/edi_attr_set/edi-attr-set-providers-dialog.component.html new file mode 100644 index 0000000000..6fe852400a --- /dev/null +++ b/Open-ILS/src/eg2/src/app/staff/admin/acq/edi_attr_set/edi-attr-set-providers-dialog.component.html @@ -0,0 +1,16 @@ + + + + + diff --git a/Open-ILS/src/eg2/src/app/staff/admin/acq/edi_attr_set/edi-attr-set-providers-dialog.component.ts b/Open-ILS/src/eg2/src/app/staff/admin/acq/edi_attr_set/edi-attr-set-providers-dialog.component.ts new file mode 100644 index 0000000000..fad5b63eaa --- /dev/null +++ b/Open-ILS/src/eg2/src/app/staff/admin/acq/edi_attr_set/edi-attr-set-providers-dialog.component.ts @@ -0,0 +1,41 @@ +import {Component, Input, ViewChild, TemplateRef, OnInit} from '@angular/core'; +import {DialogComponent} from '@eg/share/dialog/dialog.component'; +import {NgForm} from '@angular/forms'; +import {IdlService, IdlObject} from '@eg/core/idl.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 {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 {PermService} from '@eg/core/perm.service'; +import {EdiAttrSetProvidersComponent} from './edi-attr-set-providers.component'; + +@Component({ + selector: 'eg-edi-attr-set-providers-dialog', + templateUrl: './edi-attr-set-providers-dialog.component.html' +}) + +export class EdiAttrSetProvidersDialogComponent + extends DialogComponent implements OnInit { + + @Input() attrSetId: number; + + constructor( + private idl: IdlService, + private evt: EventService, + private net: NetService, + private auth: AuthService, + private pcrud: PcrudService, + private perm: PermService, + private toast: ToastService, + private modal: NgbModal + ) { + super(modal); + } + + ngOnInit() { } + +} diff --git a/Open-ILS/src/eg2/src/app/staff/admin/acq/edi_attr_set/edi-attr-set-providers.component.html b/Open-ILS/src/eg2/src/app/staff/admin/acq/edi_attr_set/edi-attr-set-providers.component.html new file mode 100644 index 0000000000..21afc96bd5 --- /dev/null +++ b/Open-ILS/src/eg2/src/app/staff/admin/acq/edi_attr_set/edi-attr-set-providers.component.html @@ -0,0 +1,11 @@ + + + {{row.name()}} + + + + + diff --git a/Open-ILS/src/eg2/src/app/staff/admin/acq/edi_attr_set/edi-attr-set-providers.component.ts b/Open-ILS/src/eg2/src/app/staff/admin/acq/edi_attr_set/edi-attr-set-providers.component.ts new file mode 100644 index 0000000000..29f6fa3670 --- /dev/null +++ b/Open-ILS/src/eg2/src/app/staff/admin/acq/edi_attr_set/edi-attr-set-providers.component.ts @@ -0,0 +1,98 @@ +import {Component, Input, ViewChild, TemplateRef, OnInit} from '@angular/core'; +import {DialogComponent} from '@eg/share/dialog/dialog.component'; +import {NgForm} from '@angular/forms'; +import {IdlService, IdlObject} from '@eg/core/idl.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 {GridDataSource, GridCellTextGenerator} from '@eg/share/grid/grid'; +import {GridComponent} from '@eg/share/grid/grid.component'; +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 {PermService} from '@eg/core/perm.service'; + +@Component({ + selector: 'eg-edi-attr-set-providers', + templateUrl: './edi-attr-set-providers.component.html' +}) + +export class EdiAttrSetProvidersComponent + extends DialogComponent implements OnInit { + + @Input() attrSetId: number; + @ViewChild('grid', { static: false }) grid: GridComponent; + attrSet: IdlObject; + dataSource: GridDataSource; + cellTextGenerator: GridCellTextGenerator; + + constructor( + private idl: IdlService, + private evt: EventService, + private net: NetService, + private auth: AuthService, + private pcrud: PcrudService, + private perm: PermService, + private toast: ToastService, + private modal: NgbModal + ) { + super(modal); + this.dataSource = new GridDataSource(); + } + + ngOnInit() { + this.attrSet = null; + this._initRecord(); + this.cellTextGenerator = { + name: row => row.name() + }; + } + + private _initRecord() { + this.attrSet = null; + let providerIds = []; + this.pcrud.retrieve('aeas', this.attrSetId, { + flesh: 1, + flesh_fields: { aeas: ['edi_accounts'] } + }).subscribe(res => { + this.attrSet = res; + providerIds = res.edi_accounts().map(r => r.provider()); + this.dataSource.getRows = (pager: Pager, sort: any[]) => { + + const idlClass = 'acqpro'; + const orderBy: any = {}; + if (sort.length) { + // Sort specified from grid + orderBy[idlClass] = sort[0].name + ' ' + sort[0].dir; + } + + const searchOps = { + offset: pager.offset, + limit: pager.limit, + order_by: orderBy, + flesh: 1, + flesh_fields: { + acqpro: ['owner'] + } + }; + const reqOps = { }; + + const search: any = new Array(); + search.push({ id: providerIds }); + const orgFilter: any = {}; + + 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(idlClass, search, searchOps, reqOps); + }; + this.grid.reload(); + }); + } + +} diff --git a/Open-ILS/src/eg2/src/app/staff/admin/acq/edi_attr_set/edi-attr-sets.component.html b/Open-ILS/src/eg2/src/app/staff/admin/acq/edi_attr_set/edi-attr-sets.component.html new file mode 100644 index 0000000000..b0e2681a76 --- /dev/null +++ b/Open-ILS/src/eg2/src/app/staff/admin/acq/edi_attr_set/edi-attr-sets.component.html @@ -0,0 +1,101 @@ + + + + + +{{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/edi_attr_set/edi-attr-sets.component.ts b/Open-ILS/src/eg2/src/app/staff/admin/acq/edi_attr_set/edi-attr-sets.component.ts new file mode 100644 index 0000000000..fce5fdba08 --- /dev/null +++ b/Open-ILS/src/eg2/src/app/staff/admin/acq/edi_attr_set/edi-attr-sets.component.ts @@ -0,0 +1,178 @@ +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 {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 {Observable, of} from 'rxjs'; +import {map, mergeMap} from 'rxjs/operators'; +import {StringComponent} from '@eg/share/string/string.component'; +import {EdiAttrSetProvidersDialogComponent} from './edi-attr-set-providers-dialog.component'; +import {EdiAttrSetEditDialogComponent} from './edi-attr-set-edit-dialog.component'; +import {AlertDialogComponent} from '@eg/share/dialog/alert.component'; +import {ConfirmDialogComponent} from '@eg/share/dialog/confirm.component'; + +@Component({ + templateUrl: './edi-attr-sets.component.html' +}) + +export class EdiAttrSetsComponent extends AdminPageComponent implements OnInit { + idlClass = 'aeas'; + classLabel: string; + + @ViewChild('grid', { static: true }) grid: GridComponent; + @ViewChild('ediAttrSetProvidersDialog', { static: false }) ediAttrSetProvidersDialog: EdiAttrSetProvidersDialogComponent; + @ViewChild('ediAttrSetEditDialog', { static: false }) ediAttrSetEditDialog: EdiAttrSetEditDialogComponent; + @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.notOneSelectedRow = (rows: IdlObject[]) => (rows.length !== 1); + this.cellTextGenerator = { + view_providers: row => '', + num_providers: row => '', + }; + this.fieldOrder = 'label'; + this.defaultNewRecord = this.idl.create('aeas'); + + 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: { + aeas: ['edi_accounts'] + } + }; + const reqOps = { }; + + 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.countProviders(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.countProviders(row))); + }; + + super.ngOnInit(); + + this.classLabel = this.idlClassDef.label; + this.includeOrgDescendants = true; + } + + countProviders(row: IdlObject): Observable { + row['num_providers'] = (new Set( row.edi_accounts().map(r => r.provider()) )).size; + return of(row); + } + + openEdiAttrSetProvidersDialog(id: number) { + this.ediAttrSetProvidersDialog.attrSetId = id; + this.ediAttrSetProvidersDialog.open({size: 'lg'}); + } + + deleteIfPossible(rows: IdlObject[]) { + if (rows.length > 0) { + if (rows[0].num_providers > 0) { + this.alertDialog.open(); + } else { + this.confirmDel.open().subscribe(confirmed => { + if (!confirmed) { return; } + super.deleteSelected([ rows[0] ]); + }); + } + } + } + + showEditAttrSetDialog(successString: StringComponent, failString: StringComponent): Promise { + return new Promise((resolve, reject) => { + this.ediAttrSetEditDialog.open({size: 'lg', scrollable: true}).subscribe( + result => { + this.successString.current() + .then(str => this.toast.success(str)); + this.grid.reload(); + resolve(result); + }, + error => { + this.updateFailedString.current() + .then(str => this.toast.danger(str)); + reject(error); + } + ); + }); + } + + createNew() { + this.ediAttrSetEditDialog.mode = 'create'; + this.showEditAttrSetDialog(this.createString, this.createErrString); + } + + editSelected(rows: IdlObject[]) { + if (rows.length <= 0) { return; } + this.ediAttrSetEditDialog.mode = 'update'; + this.ediAttrSetEditDialog.attrSetId = rows[0].id(); + this.showEditAttrSetDialog(this.successString, this.updateFailedString); + } + + cloneSelected(rows: IdlObject[]) { + if (rows.length <= 0) { return; } + this.ediAttrSetEditDialog.mode = 'clone'; + this.ediAttrSetEditDialog.cloneSource = rows[0].id(); + this.showEditAttrSetDialog(this.createString, this.createErrString); + } +} diff --git a/Open-ILS/src/eg2/src/app/staff/admin/acq/edi_attr_set/edi-attr-sets.module.ts b/Open-ILS/src/eg2/src/app/staff/admin/acq/edi_attr_set/edi-attr-sets.module.ts new file mode 100644 index 0000000000..53d0967bc0 --- /dev/null +++ b/Open-ILS/src/eg2/src/app/staff/admin/acq/edi_attr_set/edi-attr-sets.module.ts @@ -0,0 +1,29 @@ +import {NgModule} from '@angular/core'; +import {StaffCommonModule} from '@eg/staff/common.module'; +import {AdminCommonModule} from '@eg/staff/admin/common.module'; +import {EdiAttrSetsRoutingModule} from './routing.module'; +import {EdiAttrSetsComponent} from './edi-attr-sets.component'; +import {EdiAttrSetProvidersDialogComponent} from './edi-attr-set-providers-dialog.component'; +import {EdiAttrSetProvidersComponent} from './edi-attr-set-providers.component'; +import {EdiAttrSetEditDialogComponent} from './edi-attr-set-edit-dialog.component'; + +@NgModule({ + declarations: [ + EdiAttrSetsComponent, + EdiAttrSetProvidersDialogComponent, + EdiAttrSetProvidersComponent, + EdiAttrSetEditDialogComponent + ], + imports: [ + StaffCommonModule, + AdminCommonModule, + EdiAttrSetsRoutingModule + ], + exports: [ + ], + providers: [ + ] +}) + +export class EdiAttrSetsModule { +} diff --git a/Open-ILS/src/eg2/src/app/staff/admin/acq/edi_attr_set/routing.module.ts b/Open-ILS/src/eg2/src/app/staff/admin/acq/edi_attr_set/routing.module.ts new file mode 100644 index 0000000000..416fc5b768 --- /dev/null +++ b/Open-ILS/src/eg2/src/app/staff/admin/acq/edi_attr_set/routing.module.ts @@ -0,0 +1,15 @@ +import {NgModule} from '@angular/core'; +import {RouterModule, Routes} from '@angular/router'; +import {EdiAttrSetsComponent} from './edi-attr-sets.component'; + +const routes: Routes = [{ + path: '', + component: EdiAttrSetsComponent +}]; + +@NgModule({ + imports: [RouterModule.forChild(routes)], + exports: [RouterModule] +}) + +export class EdiAttrSetsRoutingModule {} diff --git a/Open-ILS/src/eg2/src/app/staff/admin/acq/routing.module.ts b/Open-ILS/src/eg2/src/app/staff/admin/acq/routing.module.ts index 00d83a4d45..a1a011e22e 100644 --- a/Open-ILS/src/eg2/src/app/staff/admin/acq/routing.module.ts +++ b/Open-ILS/src/eg2/src/app/staff/admin/acq/routing.module.ts @@ -32,6 +32,10 @@ const routes: Routes = [{ path: 'claim_type', redirectTo: 'claiming' // from legacy auto-generated admin page }, { + path: 'edi_attr_set', + loadChildren: () => + import('./edi_attr_set/edi-attr-sets.module').then(m => m.EdiAttrSetsModule) +}, { path: 'funds', loadChildren: () => import('./funds/funds.module').then(m => m.FundsModule)