LP 1855781 Circulation Policy Configuration
authorZavier Banks <zbanks@catalyte.io>
Mon, 9 Dec 2019 20:20:06 +0000 (20:20 +0000)
committerJane Sandberg <js7389@princeton.edu>
Wed, 3 May 2023 12:50:40 +0000 (05:50 -0700)
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 <zbanks@catalyte.io>
Signed-off-by: Jane Sandberg <js7389@princeton.edu>
Open-ILS/src/eg2/src/app/staff/admin/local/admin-local-splash.component.html
Open-ILS/src/eg2/src/app/staff/admin/local/circ_matrix_matchpoint/circ-matrix-matchpoint.component.html [new file with mode: 0644]
Open-ILS/src/eg2/src/app/staff/admin/local/circ_matrix_matchpoint/circ-matrix-matchpoint.component.ts [new file with mode: 0644]
Open-ILS/src/eg2/src/app/staff/admin/local/circ_matrix_matchpoint/circ-matrix-matchpoint.module.ts [new file with mode: 0644]
Open-ILS/src/eg2/src/app/staff/admin/local/circ_matrix_matchpoint/linked-circ-limit-sets.component.html [new file with mode: 0644]
Open-ILS/src/eg2/src/app/staff/admin/local/circ_matrix_matchpoint/linked-circ-limit-sets.component.ts [new file with mode: 0644]
Open-ILS/src/eg2/src/app/staff/admin/local/circ_matrix_matchpoint/routing.module.ts [new file with mode: 0644]
Open-ILS/src/eg2/src/app/staff/admin/local/routing.module.ts

index 13be7f8..9ec761b 100644 (file)
@@ -20,7 +20,7 @@
     <eg-link-table-link i18n-label label="Circulation Limit Sets" 
       routerLink="/staff/admin/local/config/circ_limit_set"></eg-link-table-link>
     <eg-link-table-link i18n-label label="Circulation Policies" 
-      url="/eg/staff/admin/local/config/circ_matrix_matchpoint"></eg-link-table-link>
+      url="/eg2/en-US/staff/admin/local/config/circ_matrix_matchpoint"></eg-link-table-link>
     <eg-link-table-link i18n-label label="Closed Dates Editor" 
       url="/eg/staff/admin/local/actor/closed_dates"></eg-link-table-link>
     <eg-link-table-link i18n-label label="Course Reserves List"  
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 (file)
index 0000000..925a022
--- /dev/null
@@ -0,0 +1,39 @@
+<eg-title i18n-prefix prefix="Circulation Policy Configuration"></eg-title>
+<eg-staff-banner bannerText="Circulation Policy Configuration" i18n-bannerText>
+</eg-staff-banner>
+
+<eg-string #successString i18n-text text="Circulation Policy Update Succeeded"></eg-string>
+<eg-string #createString i18n-text text="Circulation Policy Creation Succeeded"></eg-string>
+
+<div #limitSets>
+  <ng-container >
+      <linked-circ-limit-sets
+        #circLimitSets
+        (outputLinkedLimitSet)="setLimitSets($event)" >
+      </linked-circ-limit-sets>
+  </ng-container>
+</div>
+
+<eg-grid #grid idlClass="ccmm"
+    [dataSource]="dataSource"
+    [sortable]="true"
+    (onRowActivate)="editSelected([$event])"
+    [showFields]='"is_renewal,active,org_unit,copy_circ_lib,copy_owning_lib,user_home_ou"'
+    >
+    <eg-grid-toolbar-button 
+      label="New Circ Matrix Matchpoint" i18n-label (onClick)="createNew()">
+    </eg-grid-toolbar-button>
+    <eg-grid-toolbar-action label="Edit Selected" i18n-label (onClick)="editSelected($event)">
+    </eg-grid-toolbar-action>
+  </eg-grid>
+
+  
+<eg-fm-record-editor #editDialog
+    idlClass="ccmm" 
+    [preloadLinkedValues]="true" 
+    readonlyFields="name"
+    (recordSaved)="configureLimitSets($event); clearLinkedCircLimitSets()"
+    (recordCanceled)="clearLinkedCircLimitSets()"
+    (recordError)="clearLinkedCircLimitSets()"
+    >
+</eg-fm-record-editor>
\ 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 (file)
index 0000000..3c6b060
--- /dev/null
@@ -0,0 +1,211 @@
+import {Pager} from '@eg/share/util/pager';
+import {Component, OnInit, 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 {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 = {};
+
+    
+    @ViewChild('limitSets', { static: false }) limitSets: ElementRef;
+    @ViewChild('circLimitSets', { static: true }) limitSetsComponent: LinkedCircLimitSetsComponent;
+    @ViewChild('editDialog', { static: true }) editDialog: FmRecordEditorComponent;
+    @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 = [];
+    }
+
+    showEditDialog(field: IdlObject): Promise<any> {
+        this.limitSetsComponent.showLinkLimitSets = true;
+        this.getLimitSets(field.id());
+        this.editDialog.mode = 'update';
+        this.editDialog.recordId = field['id']();
+        return new Promise((resolve, reject) => {
+            this.editDialog.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);
+                }
+            );
+            const modalBody = document.getElementsByClassName("modal-body");
+            modalBody[modalBody.length-1].appendChild(this.limitSets.nativeElement)
+        })
+    }
+
+    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());
+    }
+
+    createNew() {
+        this.getLimitSets(null);
+        this.limitSetsComponent.showLinkLimitSets = true;
+        this.editDialog.mode = 'create';
+        // We reuse the same editor for all actions.  Be sure
+        // create action does not try to modify an existing record.
+        this.editDialog.recordId = null;
+        this.editDialog.record = null;
+        this.editDialog.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;
+            }
+        );
+        const modalBody = document.getElementsByClassName("modal-body");
+        modalBody[modalBody.length-1].appendChild(this.limitSets.nativeElement)
+    }
+
+    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) =>{
+            let 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.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.push(res.limit_set());
+            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 (file)
index 0000000..f4299aa
--- /dev/null
@@ -0,0 +1,27 @@
+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'
+
+@NgModule({
+  declarations: [
+    CircMatrixMatchpointComponent,
+    LinkedCircLimitSetsComponent
+  ],
+  imports: [
+    AdminCommonModule,
+    CircMatrixMatchpointRoutingModule,
+    TreeModule
+  ],
+  exports: [
+  ],
+  providers: [
+  ]
+})
+
+export class CircMatrixMathpointModule {
+}
+
+
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 (file)
index 0000000..1ca4821
--- /dev/null
@@ -0,0 +1,67 @@
+<div *ngIf="showLinkLimitSets">
+    <div class="col-lg-15 modal-header bg-info d-flex justify-content-center"><h3 class="modal-title mt-3 mb-3">Linked Limit Sets</h3></div>
+    <ng-container *ngIf="getObjectKeys().length > 0">
+        <ng-container *ngFor="let key of getObjectKeys(); let i = index">
+            <div *ngIf="!linkedSetList[i].isDeleted" class="col-lg-15 d-flex justify-content-center">
+                <div *ngIf="linkedSetList[i].created" class="col-lg-2 mt-3 mb-3 form-group form-check">
+                    <label for="name">Name</label>
+                    <div class="d-flex justify-content-center">
+                        <span>{{limitSetNames[linkedSetList[i].linkedLimitSet.limit_set()]}}</span>
+                    </div>
+                </div>
+                <div *ngIf="linkedSetList[i].created" class="col-lg-2 mt-3 mb-3 form-group form-check">
+                    <label for="{{fallthrough+i}}">Fallthrough</label>
+                    <div class="d-flex justify-content-center">
+                        <input
+                            class="form-check-input"
+                            type="checkbox"
+                            name="{{fallthrough+i}}"
+                            [ngModel]="linkedSetList[i].linkedLimitSet.fallthrough()" 
+                            (ngModelChange)="linkedSetList[i].linkedLimitSet.fallthrough($event); this.emitLimitSet();"/>
+                    </div>
+                </div>
+                <div *ngIf="linkedSetList[i].created" class="col-lg-2 mt-3 mb-3 form-group form-check">
+                    <label for="{{active+i}}">Active</label>
+                    <div class="d-flex justify-content-center">
+                        <input
+                        class="form-check-input"
+                        type="checkbox"
+                        [ngModel]="linkedSetList[i].linkedLimitSet.active()"
+                        (ngModelChange)="linkedSetList[i].linkedLimitSet.active($event); this.emitLimitSet();"/>
+                    </div>
+                    
+                </div>
+                <div *ngIf="linkedSetList[i].created" class="col-lg-2 mt-3 mb-3 form-group form-check">
+                    <button
+                        type="button"
+                        class="btn btn-warning"
+                        ng-disabled="!linkedSet"
+                        (click)="removeLinkedSet(i)"
+                        i18n-title title="Remove">Remove
+                    </button>
+                </div>
+            </div>
+        </ng-container>
+    </ng-container>
+    <div class="input-group mt-3">
+        <div class="input-group-prepends col-lg-9">
+            <div class="input-group-text  col-lg-4">
+                <label for="linkedLimitName" i18n>Circ Limit Set Name </label>
+            </div>
+            <eg-combobox 
+                name="linkedLimitName"
+                idlClass="ccls" 
+                idlField="name" 
+                (onChange)="onChange($event)">
+            </eg-combobox>
+            <div>
+                <button
+                    type="button"
+                    class="btn btn-info"
+                    (click)="addLinkedSet()"
+                    i18n-title title="Add">Add
+                </button>
+            </div>
+        </div>
+    </div>
+</div>
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 (file)
index 0000000..665ceea
--- /dev/null
@@ -0,0 +1,100 @@
+
+import {Component, OnInit, Input,Output,EventEmitter} from '@angular/core';
+import { IdlService} from '@eg/core/idl.service';
+class LinkedLimitSetObjects {
+    linkedLimitSet:any;
+    created:boolean;
+    isDeleted:boolean;
+    isNew:boolean;
+}
+
+@Component({
+    selector:'linked-circ-limit-sets',
+    templateUrl: './linked-circ-limit-sets.component.html'
+})
+
+
+export class LinkedCircLimitSetsComponent implements OnInit {
+
+    @Input() usedSetLimitList = [];
+    @Input() limitSetNames = {};
+    @Output() outputLinkedLimitSet: EventEmitter<any>;
+    linkedSetList = {};
+    linkedSet:any;
+    showLinkLimitSets:boolean
+
+    constructor( 
+        private idl: IdlService
+        ) {
+        this.outputLinkedLimitSet = new EventEmitter();
+    }
+
+    ngOnInit() {}
+
+    displayLinkedLimitSets() {
+        this.createEmptyLimitSetObject()
+    }
+
+    createFilledLimitSetObject(element) {
+        let 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() {
+        let object = this.idl.create("ccmlsm")
+        let 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.find(element => element == 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.push(this.linkedSet.id)
+            }
+        }
+    }
+
+    emitLimitSet() {
+        this.outputLinkedLimitSet.emit(this.linkedSetList)
+    }
+
+    removeLinkedSet(index) {
+        if(!this.linkedSetList[index].isNew) {
+            this.usedSetLimitList.splice(index,1);
+        }
+        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 (file)
index 0000000..5690d3c
--- /dev/null
@@ -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 {}
\ No newline at end of file
index ac64960..b79c7a9 100644 (file)
@@ -100,6 +100,9 @@ const routes: Routes = [{
     loadChildren: () =>
       import('./negative-balances/negative-balances.module').then(m => m.NegativeBalancesModule)
 }, {
+    path: 'config/circ_matrix_matchpoint',
+    loadChildren: '@eg/staff/admin/local/circ_matrix_matchpoint/circ-matrix-matchpoint.module#CircMatrixMathpointModule'
+}, {
     path: 'asset/stat_cat',
     component: BasicAdminPageComponent,
     data: [{