LP1821382 Make items bookable (part 2)
authorBill Erickson <berickxx@gmail.com>
Wed, 27 Mar 2019 14:50:36 +0000 (10:50 -0400)
committerDan Wells <dbw2@calvin.edu>
Wed, 29 May 2019 19:30:51 +0000 (15:30 -0400)
Including support for passing filters and default context org values
to admin page grids.

Signed-off-by: Bill Erickson <berickxx@gmail.com>
Signed-off-by: Dan Wells <dbw2@calvin.edu>
Open-ILS/src/eg2/src/app/staff/share/admin-page/admin-page.component.ts
Open-ILS/src/eg2/src/app/staff/share/booking/make-bookable-dialog.component.html
Open-ILS/src/eg2/src/app/staff/share/booking/make-bookable-dialog.component.ts

index 125d3a0..955e95a 100644 (file)
@@ -1,4 +1,5 @@
 import {Component, Input, OnInit, TemplateRef, ViewChild} from '@angular/core';
+import {ActivatedRoute} from '@angular/router';
 import {IdlService, IdlObject} from '@eg/core/idl.service';
 import {GridDataSource} from '@eg/share/grid/grid';
 import {GridComponent} from '@eg/share/grid/grid.component';
@@ -90,7 +91,12 @@ export class AdminPageComponent implements OnInit {
     viewPerms: string;
     canCreate: boolean;
 
+    // Filters may be passed via URL query param.
+    // They are used to augment the grid data search query.
+    gridFilters: {[key: string]: string | number};
+
     constructor(
+        private route: ActivatedRoute,
         private idl: IdlService,
         private org: OrgService,
         private auth: AuthService,
@@ -101,7 +107,7 @@ export class AdminPageComponent implements OnInit {
         this.translatableFields = [];
     }
 
-    applyOrgValues() {
+    applyOrgValues(orgId?: number) {
 
         if (this.disableOrgFilter) {
             this.orgField = null;
@@ -121,7 +127,7 @@ export class AdminPageComponent implements OnInit {
 
         if (this.orgField) {
             this.orgFieldLabel = this.idlClassDef.field_map[this.orgField].label;
-            this.contextOrg = this.org.root();
+            this.contextOrg = this.org.get(orgId) || this.org.root();
         }
     }
 
@@ -139,6 +145,16 @@ export class AdminPageComponent implements OnInit {
                 this.idlClassDef.table;
         }
 
+        // gridFilters are a JSON encoded string
+        const filters = this.route.snapshot.queryParamMap.get('gridFilters');
+        if (filters) {
+            try {
+                this.gridFilters = JSON.parse(filters);
+            } catch (E) {
+                console.error('Invalid grid filters provided: ', filters)
+            }
+        }
+
         // Limit the view org selector to orgs where the user has
         // permacrud-encoded view permissions.
         const pc = this.idlClassDef.permacrud;
@@ -146,8 +162,9 @@ export class AdminPageComponent implements OnInit {
             this.viewPerms = pc.retrieve.perms;
         }
 
+        const contextOrg = this.route.snapshot.queryParamMap.get('contextOrg');
         this.checkCreatePerms();
-        this.applyOrgValues();
+        this.applyOrgValues(Number(contextOrg));
 
         // If the caller provides not data source, create a generic one.
         if (!this.dataSource) {
@@ -288,6 +305,14 @@ export class AdminPageComponent implements OnInit {
                 order_by: orderBy
             };
 
+            if (!this.contextOrg && !this.gridFilters) {
+                // No org filter -- fetch all rows
+                return this.pcrud.retrieveAll(
+                    this.idlClass, searchOps, {fleshSelectors: true});
+            }
+
+            const search: any = {};
+
             if (this.contextOrg) {
                 // Filter rows by those linking to the context org and
                 // optionally ancestor and descendant org units.
@@ -304,15 +329,18 @@ export class AdminPageComponent implements OnInit {
                         this.org.descendants(this.contextOrg, true));
                 }
 
-                const search = {};
                 search[this.orgField] = orgs;
-                return this.pcrud.search(
-                    this.idlClass, search, searchOps, {fleshSelectors: true});
             }
 
-            // No org filter -- fetch all rows
-            return this.pcrud.retrieveAll(
-                this.idlClass, searchOps, {fleshSelectors: true});
+            if (this.gridFilters) {
+                // Lay the URL grid filters over our search object.
+                Object.keys(this.gridFilters).forEach(key => {
+                    search[key] = this.gridFilters[key];
+                });
+            }
+
+            return this.pcrud.search(
+                this.idlClass, search, searchOps, {fleshSelectors: true});
         };
     }
 
index cc03fc6..25ad596 100644 (file)
       </button>
     </div>
     <div class="modal-body">
-      <div class="row">
-        <div class="col-lg-12 d-flex justify-content-center">
+      <div class="row" *ngIf="!updateComplete">
+        <div class="col-lg-12">
           <span i18n>Make {{copyIds.length}} Item(s) Bookable?</span>
         </div>
       </div>
+      <div class="row" *ngIf="updateComplete">
+        <div class="col-lg-12 d-flex flex-column">
+          <div i18n>{{numSucceeded}} Item(s) Made Bookable.</div>
+          <div class="mt-2">
+            <a target="_blank" routerLink="/staff/admin/booking/resource"
+              [queryParams]="manageUrlParams()" i18n>
+              Manage Bookable Resources
+            </a>
+          </div>
+        </div>
+      </div>
     </div>
     <div class="modal-footer">
       <ng-container>
index 84d7941..9df3f9f 100644 (file)
@@ -1,5 +1,4 @@
-import {Component, OnInit, OnDestroy, Input, ViewChild,
-        Renderer2} from '@angular/core';
+import {Component, OnInit, OnDestroy, Input, ViewChild} from '@angular/core';
 import {Subscription} from 'rxjs';
 import {IdlObject} from '@eg/core/idl.service';
 import {NetService} from '@eg/core/net.service';
@@ -7,7 +6,7 @@ import {EventService} from '@eg/core/event.service';
 import {PcrudService} from '@eg/core/pcrud.service';
 import {ToastService} from '@eg/share/toast/toast.service';
 import {AuthService} from '@eg/core/auth.service';
-import {NgbModal, NgbModalOptions} from '@ng-bootstrap/ng-bootstrap';
+import {NgbModal} from '@ng-bootstrap/ng-bootstrap';
 import {DialogComponent} from '@eg/share/dialog/dialog.component';
 import {StringComponent} from '@eg/share/string/string.component';
 
@@ -30,6 +29,8 @@ export class MakeBookableDialogComponent
     numSucceeded: number;
     numFailed: number;
     updateComplete: boolean;
+    newResourceType: number;
+    newResourceOrg: number;
 
     onOpenSub: Subscription;
 
@@ -42,7 +43,6 @@ export class MakeBookableDialogComponent
         private net: NetService,
         private pcrud: PcrudService,
         private evt: EventService,
-        private renderer: Renderer2,
         private auth: AuthService) {
         super(modal); // required for subclassing
     }
@@ -58,6 +58,54 @@ export class MakeBookableDialogComponent
     ngOnDestroy() {
         this.onOpenSub.unsubscribe();
     }
+
+    manageUrlParams(): any {
+        if (this.newResourceOrg) {
+            return {
+                gridFilters: JSON.stringify({type: this.newResourceType}),
+                contextOrg: this.newResourceOrg
+            };
+        }
+    }
+
+    makeBookable() {
+        this.newResourceType = null;
+
+        this.net.request(
+            'open-ils.booking',
+            'open-ils.booking.resources.create_from_copies',
+            this.auth.token(), this.copyIds
+        ).toPromise().then(
+            resp => {
+                // resp.brsrc = [[brsrc.id, acp.id, existed], ...]
+                // resp.brt = [[brt.id, brt.peer_record, existed], ...]
+                const evt = this.evt.parse(resp);
+                if (evt) { return Promise.reject(evt); }
+                this.numSucceeded = resp.brsrc.length;
+                this.newResourceType = resp.brt[0][0]; // new resource ID
+                this.updateComplete = true;
+                this.successMsg.current().then(msg => this.toast.success(msg));
+            },
+            err => Promise.reject(err)
+        ).then(
+            ok => {
+                // Once resource creation is complete, grab the call number
+                // for the first copy to get the owning library
+                this.pcrud.retrieve('acp', this.copyIds[0],
+                    {flesh: 1, flesh_fields: {acp: ['call_number']}})
+                .toPromise().then(copy => {
+                    this.newResourceOrg = copy.call_number().owning_lib();
+                    this.updateComplete = true;
+                });
+            },
+            err => {
+                console.error(err);
+                this.numFailed++;
+                this.errorMsg.current().then(msg => this.toast.danger(msg));
+                this.updateComplete = true;
+            }
+        );
+    }
 }