From a79019eee070ead321999d18b6d608d25cd6c06d Mon Sep 17 00:00:00 2001 From: Zavier Banks Date: Mon, 9 Dec 2019 20:20:06 +0000 Subject: [PATCH] LP 1855781 Circulation Policy Configuration I have ported over the circulation policy configuration from dojo to Angular2. Implementing, not only CRUD functionality, but a a componenet for linking sets to specified cirulation policy matchpoints. Signed-off-by: Zavier Banks Signed-off-by: Jane Sandberg --- .../admin/local/admin-local-splash.component.html | 2 +- .../circ-matrix-matchpoint-dialog.component.html | 9 + .../circ-matrix-matchpoint-dialog.component.ts | 53 +++++ .../circ-matrix-matchpoint.component.html | 94 +++++++++ .../circ-matrix-matchpoint.component.ts | 216 +++++++++++++++++++++ .../circ-matrix-matchpoint.module.ts | 29 +++ .../linked-circ-limit-sets.component.html | 70 +++++++ .../linked-circ-limit-sets.component.ts | 106 ++++++++++ .../local/circ_matrix_matchpoint/routing.module.ts | 14 ++ .../src/app/staff/admin/local/routing.module.ts | 4 + 10 files changed, 596 insertions(+), 1 deletion(-) create mode 100644 Open-ILS/src/eg2/src/app/staff/admin/local/circ_matrix_matchpoint/circ-matrix-matchpoint-dialog.component.html create mode 100644 Open-ILS/src/eg2/src/app/staff/admin/local/circ_matrix_matchpoint/circ-matrix-matchpoint-dialog.component.ts create mode 100644 Open-ILS/src/eg2/src/app/staff/admin/local/circ_matrix_matchpoint/circ-matrix-matchpoint.component.html create mode 100644 Open-ILS/src/eg2/src/app/staff/admin/local/circ_matrix_matchpoint/circ-matrix-matchpoint.component.ts create mode 100644 Open-ILS/src/eg2/src/app/staff/admin/local/circ_matrix_matchpoint/circ-matrix-matchpoint.module.ts create mode 100644 Open-ILS/src/eg2/src/app/staff/admin/local/circ_matrix_matchpoint/linked-circ-limit-sets.component.html create mode 100644 Open-ILS/src/eg2/src/app/staff/admin/local/circ_matrix_matchpoint/linked-circ-limit-sets.component.ts create mode 100644 Open-ILS/src/eg2/src/app/staff/admin/local/circ_matrix_matchpoint/routing.module.ts diff --git a/Open-ILS/src/eg2/src/app/staff/admin/local/admin-local-splash.component.html b/Open-ILS/src/eg2/src/app/staff/admin/local/admin-local-splash.component.html index 223d1817cc..52d30662ef 100644 --- a/Open-ILS/src/eg2/src/app/staff/admin/local/admin-local-splash.component.html +++ b/Open-ILS/src/eg2/src/app/staff/admin/local/admin-local-splash.component.html @@ -20,7 +20,7 @@ + routerLink="/staff/admin/local/config/circ_matrix_matchpoint"> diff --git a/Open-ILS/src/eg2/src/app/staff/admin/local/circ_matrix_matchpoint/circ-matrix-matchpoint-dialog.component.html b/Open-ILS/src/eg2/src/app/staff/admin/local/circ_matrix_matchpoint/circ-matrix-matchpoint-dialog.component.html new file mode 100644 index 0000000000..d21bcd5777 --- /dev/null +++ b/Open-ILS/src/eg2/src/app/staff/admin/local/circ_matrix_matchpoint/circ-matrix-matchpoint-dialog.component.html @@ -0,0 +1,9 @@ + + + \ No newline at end of file diff --git a/Open-ILS/src/eg2/src/app/staff/admin/local/circ_matrix_matchpoint/circ-matrix-matchpoint-dialog.component.ts b/Open-ILS/src/eg2/src/app/staff/admin/local/circ_matrix_matchpoint/circ-matrix-matchpoint-dialog.component.ts new file mode 100644 index 0000000000..a26506ad11 --- /dev/null +++ b/Open-ILS/src/eg2/src/app/staff/admin/local/circ_matrix_matchpoint/circ-matrix-matchpoint-dialog.component.ts @@ -0,0 +1,53 @@ +import {Component, OnInit, Input, + Output, EventEmitter} from '@angular/core'; +import {NgbModal, NgbModalOptions} from '@ng-bootstrap/ng-bootstrap'; +import {Observable} from 'rxjs'; +import {DialogComponent} from '@eg/share/dialog/dialog.component'; + +@Component({ + selector: 'eg-circ-matrix-matchpoint-dialog', + templateUrl: './circ-matrix-matchpoint-dialog.component.html' +}) +export class CircMatrixMatchpointDialogComponent extends DialogComponent implements OnInit { + + // Emit the modified object when the save action completes. + @Output() recordSaved = new EventEmitter(); + + // Emit the original object when the save action is canceled. + @Output() recordCanceled = new EventEmitter(); + + constructor( + private modal: NgbModal // required for passing to parent + ) { + super(modal); + } + + ngOnInit() { + } + + open(args?: NgbModalOptions): Observable { + if (!args) { + args = {}; + } + // ensure we don't hang on to our copy of the record + // if the user dismisses the dialog + args.beforeDismiss = () => { + return true; + }; + return super.open(args); + } + + cancel() { + this.recordCanceled.emit(); + this.close(); + } + + closeEditor() { + this.recordCanceled.emit(); + this.close(); + } + + save() { + this.recordSaved.emit(); + } +} diff --git a/Open-ILS/src/eg2/src/app/staff/admin/local/circ_matrix_matchpoint/circ-matrix-matchpoint.component.html b/Open-ILS/src/eg2/src/app/staff/admin/local/circ_matrix_matchpoint/circ-matrix-matchpoint.component.html new file mode 100644 index 0000000000..9bedaa58bb --- /dev/null +++ b/Open-ILS/src/eg2/src/app/staff/admin/local/circ_matrix_matchpoint/circ-matrix-matchpoint.component.html @@ -0,0 +1,94 @@ + + + + + + + + + + + + + + + +
+ + + + +
+ + +
+ +
+
+
Circulation Policies
+
+
+ + +
+ +
+
+
Circulation Policy Effects
+
+
+ + + +
\ No newline at end of file diff --git a/Open-ILS/src/eg2/src/app/staff/admin/local/circ_matrix_matchpoint/circ-matrix-matchpoint.component.ts b/Open-ILS/src/eg2/src/app/staff/admin/local/circ_matrix_matchpoint/circ-matrix-matchpoint.component.ts new file mode 100644 index 0000000000..535aa0aaee --- /dev/null +++ b/Open-ILS/src/eg2/src/app/staff/admin/local/circ_matrix_matchpoint/circ-matrix-matchpoint.component.ts @@ -0,0 +1,216 @@ +import {Pager} from '@eg/share/util/pager'; +import {Component, OnInit, AfterViewInit, Input, ViewChild, ElementRef} from '@angular/core'; +import {GridComponent} from '@eg/share/grid/grid.component'; +import {GridDataSource, GridColumn, GridRowFlairEntry} from '@eg/share/grid/grid'; +import {IdlObject} from '@eg/core/idl.service'; +import {FmRecordEditorComponent} from '@eg/share/fm-editor/fm-editor.component'; +import {LinkedCircLimitSetsComponent} from './linked-circ-limit-sets.component'; +import {CircMatrixMatchpointDialogComponent} from './circ-matrix-matchpoint-dialog.component'; +import {StringComponent} from '@eg/share/string/string.component'; +import {PcrudService} from '@eg/core/pcrud.service'; +import {ToastService} from '@eg/share/toast/toast.service'; + + @Component({ + templateUrl: './circ-matrix-matchpoint.component.html' +}) +export class CircMatrixMatchpointComponent implements OnInit { + recId: number; + gridDataSource: GridDataSource; + initDone = false; + dataSource: GridDataSource = new GridDataSource(); + showLinkLimitSets = false; + usedSetLimitList = {}; + linkedLimitSets = []; + limitSetNames = {}; + dividerStyle = { + width: '30%', + marginTop: '25px', + marginLeft: '73%' + }; + + @ViewChild('limitSets', { static: false }) limitSets: ElementRef; + @ViewChild('circLimitSets', { static: true }) limitSetsComponent: LinkedCircLimitSetsComponent; + @ViewChild('editDialog', { static: true }) editDialog: FmRecordEditorComponent; + @ViewChild('matchpointDialog', { static: true }) matchpointDialog: CircMatrixMatchpointDialogComponent; + @ViewChild('grid', { static: true }) grid: GridComponent; + @ViewChild('successString', { static: true }) successString: StringComponent; + @ViewChild('createString', { static: false }) createString: StringComponent; + @ViewChild('createErrString', { static: false }) createErrString: StringComponent; + @ViewChild('updateFailedString', { static: false }) updateFailedString: StringComponent; + + @Input() idlClass = 'ccmm'; + // Default sort field, used when no grid sorting is applied. + @Input() sortField: string; + + @Input() dialogSize: 'sm' | 'lg' = 'lg'; + + constructor( + private pcrud: PcrudService, + private toast: ToastService + ) { + this.gridDataSource = new GridDataSource(); + } + + ngOnInit() { + this.initDone = true; + 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 + }; + return this.pcrud.retrieveAll('ccmm', searchOps, {fleshSelectors: true}); + }; + } + + clearLinkedCircLimitSets() { + this.limitSetsComponent.usedSetLimitList = {}; + this.limitSetsComponent.linkedSetList = []; + this.linkedLimitSets = []; + } + + closeDialog() { + this.matchpointDialog.closeEditor(); + this.grid.reload(); + } + + editSelected(fields: IdlObject[]) { + // Edit each IDL thing one at a time + const editOneThing = (field: IdlObject) => { + if (!field) { return; } + this.showEditDialog(field).then( + () => editOneThing(fields.shift())); + }; + editOneThing(fields.shift()); + } + + showEditDialog(field: IdlObject): Promise { + this.limitSetsComponent.showLinkLimitSets = true; + this.getLimitSets(field.id()); + this.editDialog.mode = 'update'; + this.editDialog.recordId = field['id'](); + return new Promise((resolve, reject) => { + this.matchpointDialog.open({size: this.dialogSize}).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.getLimitSets(null); + this.limitSetsComponent.showLinkLimitSets = true; + this.editDialog.mode = 'create'; + this.editDialog.recordId = null; + this.editDialog.record = null; + this.editDialog.handleRecordChange(); + // We reuse the same editor for all actions. Be sure + // create action does not try to modify an existing record. + this.matchpointDialog.open({size: this.dialogSize}).subscribe( + ok => { + this.createString.current() + .then(str => this.toast.success(str)); + this.limitSetsComponent.showLinkLimitSets = false; + this.grid.reload(); + }, + rejection => { + if (!rejection.dismissed) { + this.createErrString.current() + .then(str => this.toast.danger(str)); + } + this.limitSetsComponent.showLinkLimitSets = false; + } + ); + } + + setLimitSets(sets) { + this.linkedLimitSets = sets; + } + + /** + * Runs through the different CRUD operations, specified by the object that is passed into each. + * @param matchpoint + */ + configureLimitSets(matchpoint) { + const linkedSets = this.linkedLimitSets; + Object.keys(linkedSets).forEach((key) => { + const ls = linkedSets[key]; + if (ls.created) { + this.deleteLimitSets(ls).then(() => { + if (ls.isNew && !ls.isDeleted) { + this.pcrud.create(this.createLimitSets(ls.linkedLimitSet, matchpoint)).subscribe(() => {}); + } else if (!ls.isNew && !ls.isDeleted) { + this.updateLimitSets(ls.linkedLimitSet); + } + }); + } + }); + } + + getLimitSets(id) { + this.clearLinkedCircLimitSets(); + this.pcrud.retrieveAll('ccmlsm').subscribe((res) => { + /** + * If the limit set's matchpoint equals the matchpoint given + * by the user, then add that to the set limit list + */ + this.limitSetsComponent.usedSetLimitList[res.limit_set()] = res.id(); + if (res.matchpoint() === id) { + this.limitSetsComponent.createFilledLimitSetObject(res); + } + }); + /** + * Retrives all limit set names + */ + this.pcrud.retrieveAll('ccls').subscribe((res) => { + this.limitSetsComponent.limitSetNames[res.id()] = res.name(); + }); + } + + createLimitSets(limitSet, matchpoint) { + if (typeof matchpoint === 'number' || typeof matchpoint === 'string') { + limitSet.matchpoint(matchpoint); + } else { + limitSet.matchpoint(matchpoint.id()); + } + return limitSet; + } + + updateLimitSets(limitSet) { + this.pcrud.update(limitSet).subscribe(() => {}); + } + + deleteLimitSets(limitSet) { + return new Promise((resolve, reject) => { + if (limitSet.isDeleted) { + if (limitSet.linkedLimitSet.id()) { + this.pcrud.remove(limitSet.linkedLimitSet).subscribe(res => { + resolve(); + }); + } else { + resolve(); + } + } else { + resolve(); + } + }); + } +} diff --git a/Open-ILS/src/eg2/src/app/staff/admin/local/circ_matrix_matchpoint/circ-matrix-matchpoint.module.ts b/Open-ILS/src/eg2/src/app/staff/admin/local/circ_matrix_matchpoint/circ-matrix-matchpoint.module.ts new file mode 100644 index 0000000000..6ac7b60869 --- /dev/null +++ b/Open-ILS/src/eg2/src/app/staff/admin/local/circ_matrix_matchpoint/circ-matrix-matchpoint.module.ts @@ -0,0 +1,29 @@ +import {NgModule} from '@angular/core'; +import {TreeModule} from '@eg/share/tree/tree.module'; +import {CircMatrixMatchpointRoutingModule} from './routing.module'; +import {AdminCommonModule} from '@eg/staff/admin/common.module'; +import {CircMatrixMatchpointComponent} from './circ-matrix-matchpoint.component'; +import {LinkedCircLimitSetsComponent} from './linked-circ-limit-sets.component'; +import {CircMatrixMatchpointDialogComponent} from './circ-matrix-matchpoint-dialog.component'; + +@NgModule({ + declarations: [ + CircMatrixMatchpointComponent, + LinkedCircLimitSetsComponent, + CircMatrixMatchpointDialogComponent + ], + imports: [ + AdminCommonModule, + CircMatrixMatchpointRoutingModule, + TreeModule + ], + exports: [ + ], + providers: [ + ] +}) + +export class CircMatrixMatchpointModule { +} + + diff --git a/Open-ILS/src/eg2/src/app/staff/admin/local/circ_matrix_matchpoint/linked-circ-limit-sets.component.html b/Open-ILS/src/eg2/src/app/staff/admin/local/circ_matrix_matchpoint/linked-circ-limit-sets.component.html new file mode 100644 index 0000000000..07ef8c52a3 --- /dev/null +++ b/Open-ILS/src/eg2/src/app/staff/admin/local/circ_matrix_matchpoint/linked-circ-limit-sets.component.html @@ -0,0 +1,70 @@ + + + +
+
+ + +
+
+ +
+ {{limitSetNames[linkedSetList[i].linkedLimitSet.limit_set()]}} +
+
+
+ +
+ +
+
+
+ +
+ +
+ +
+
+ +
+
+
+
+
+
+
Circ Limit Set Name
+ + +
+
+ + +
+
+
diff --git a/Open-ILS/src/eg2/src/app/staff/admin/local/circ_matrix_matchpoint/linked-circ-limit-sets.component.ts b/Open-ILS/src/eg2/src/app/staff/admin/local/circ_matrix_matchpoint/linked-circ-limit-sets.component.ts new file mode 100644 index 0000000000..0921345ae9 --- /dev/null +++ b/Open-ILS/src/eg2/src/app/staff/admin/local/circ_matrix_matchpoint/linked-circ-limit-sets.component.ts @@ -0,0 +1,106 @@ + +import {Component, OnInit, Input, Output, EventEmitter, ViewChild} from '@angular/core'; +import { IdlService} from '@eg/core/idl.service'; +import {ToastService} from '@eg/share/toast/toast.service'; +import {StringComponent} from '@eg/share/string/string.component'; + +class LinkedLimitSetObjects { + linkedLimitSet: any; + created: boolean; + isDeleted: boolean; + isNew: boolean; +} + +@Component({ + selector: 'eg-linked-circ-limit-sets', + templateUrl: './linked-circ-limit-sets.component.html' +}) + +export class LinkedCircLimitSetsComponent implements OnInit { + + @ViewChild('errorString', { static: true }) errorString: StringComponent; + + @Input() usedSetLimitList = {}; + @Input() limitSetNames = {}; + @Output() outputLinkedLimitSet: EventEmitter; + linkedSetList = {}; + linkedSet: any; + showLinkLimitSets: boolean; + + constructor( + private idl: IdlService, + private toast: ToastService + ) { + this.outputLinkedLimitSet = new EventEmitter(); + } + + ngOnInit() {} + + displayLinkedLimitSets() { + this.createEmptyLimitSetObject(); + } + + createFilledLimitSetObject(element) { + const newLinkedSetObject = new LinkedLimitSetObjects(); + if (element.fallthrough() === 'f') { element.fallthrough(false); } + if (element.fallthrough() === 't') { element.fallthrough(true); } + if (element.active() === 'f') { element.active(false); } + if (element.active() === 't') { element.active(true); } + newLinkedSetObject.linkedLimitSet = element; + newLinkedSetObject.created = true; + newLinkedSetObject.isNew = false; + newLinkedSetObject.isDeleted = false; + this.linkedSetList[this.getObjectKeys().length] = newLinkedSetObject; + } + + createEmptyLimitSetObject() { + const object = this.idl.create('ccmlsm'); + const newLinkedSetObject = new LinkedLimitSetObjects(); + newLinkedSetObject.linkedLimitSet = object; + newLinkedSetObject.linkedLimitSet.fallthrough(false); + newLinkedSetObject.linkedLimitSet.active(true); + newLinkedSetObject.isNew = true; + newLinkedSetObject.created = false; + newLinkedSetObject.isDeleted = false; + this.linkedSetList[this.getObjectKeys().length] = newLinkedSetObject; + } + + onChange(object: any) { + this.linkedSet = object; + } + + getObjectKeys() { + if (this.linkedSetList) { + return Object.keys(this.linkedSetList); + } else { + this.linkedSetList = {}; + return Object.keys({}); + } + } + + addLinkedSet() { + if (this.linkedSet) { + if ( !this.usedSetLimitList[this.linkedSet.id]) { + this.createEmptyLimitSetObject(); + this.linkedSetList[this.getObjectKeys().length - 1].linkedLimitSet.limit_set(this.linkedSet.id); + this.linkedSetList[this.getObjectKeys().length - 1].created = true; + this.emitLimitSet(); + this.usedSetLimitList[this.linkedSet.id] = this.linkedSet.label; + } else { + this.errorString.current() + .then(str => this.toast.danger(str)); + } + } + } + + emitLimitSet() { + this.outputLinkedLimitSet.emit(this.linkedSetList); + } + + removeLinkedSet(index) { + delete this.usedSetLimitList[this.linkedSetList[index].linkedLimitSet.limit_set()]; + this.linkedSetList[index].isDeleted = true; + this.emitLimitSet(); + } +} + diff --git a/Open-ILS/src/eg2/src/app/staff/admin/local/circ_matrix_matchpoint/routing.module.ts b/Open-ILS/src/eg2/src/app/staff/admin/local/circ_matrix_matchpoint/routing.module.ts new file mode 100644 index 0000000000..193dc77d3b --- /dev/null +++ b/Open-ILS/src/eg2/src/app/staff/admin/local/circ_matrix_matchpoint/routing.module.ts @@ -0,0 +1,14 @@ +import {NgModule} from '@angular/core'; +import {RouterModule, Routes} from '@angular/router'; +import {CircMatrixMatchpointComponent} from './circ-matrix-matchpoint.component'; +const routes: Routes = [{ + path: '', + component: CircMatrixMatchpointComponent +}]; + +@NgModule({ + imports: [RouterModule.forChild(routes)], + exports: [RouterModule] +}) + +export class CircMatrixMatchpointRoutingModule {} diff --git a/Open-ILS/src/eg2/src/app/staff/admin/local/routing.module.ts b/Open-ILS/src/eg2/src/app/staff/admin/local/routing.module.ts index c29e7e643d..0ad6510859 100644 --- a/Open-ILS/src/eg2/src/app/staff/admin/local/routing.module.ts +++ b/Open-ILS/src/eg2/src/app/staff/admin/local/routing.module.ts @@ -27,6 +27,10 @@ const routes: Routes = [{ loadChildren: () => import('./survey/survey.module').then(m => m.SurveyModule) }, { + path: 'config/circ_matrix_matchpoint', + loadChildren: () => + import('./circ_matrix_matchpoint/circ-matrix-matchpoint.module').then(m => m.CircMatrixMatchpointModule) +}, { path: ':schema/:table', component: BasicAdminPageComponent }]; -- 2.11.0