LP1919465 Holds pull list Angular / Wide Holds API Port
authorBill Erickson <berickxx@gmail.com>
Fri, 12 Mar 2021 22:50:40 +0000 (17:50 -0500)
committerChris Sharp <csharp@georgialibraries.org>
Fri, 24 Sep 2021 19:20:05 +0000 (15:20 -0400)
Ports the holds pull list to Angular and takes advantage of the new Wide
Holds API.  Includes seed data for grid preferences and a new holds pull
list (server) print template.

The UI displays the total holds count and includes a org unit selector.
It pre-fetches all holds, partly to display the full count, but also
based on the assumption that a pull list will typically be used all or
none.

Signed-off-by: Bill Erickson <berickxx@gmail.com>
Signed-off-by: Jennifer Weston <jennifer.weston@equinoxOLI.org>
Signed-off-by: Chris Sharp <csharp@georgialibraries.org>
Open-ILS/src/eg2/src/app/staff/circ/holds/holds.module.ts [new file with mode: 0644]
Open-ILS/src/eg2/src/app/staff/circ/holds/pull-list.component.html [new file with mode: 0644]
Open-ILS/src/eg2/src/app/staff/circ/holds/pull-list.component.ts [new file with mode: 0644]
Open-ILS/src/eg2/src/app/staff/circ/holds/routing.module.ts [new file with mode: 0644]
Open-ILS/src/eg2/src/app/staff/circ/routing.module.ts
Open-ILS/src/eg2/src/app/staff/nav.component.html
Open-ILS/src/eg2/src/app/staff/share/holds/grid.component.html
Open-ILS/src/eg2/src/app/staff/share/holds/grid.component.ts
Open-ILS/src/sql/Pg/950.data.seed-values.sql
Open-ILS/src/sql/Pg/upgrade/XXXX.data.pull-list-print-template.sql [new file with mode: 0644]
Open-ILS/src/templates/staff/navbar.tt2

diff --git a/Open-ILS/src/eg2/src/app/staff/circ/holds/holds.module.ts b/Open-ILS/src/eg2/src/app/staff/circ/holds/holds.module.ts
new file mode 100644 (file)
index 0000000..5526c36
--- /dev/null
@@ -0,0 +1,28 @@
+import {NgModule} from '@angular/core';
+import {FmRecordEditorModule} from '@eg/share/fm-editor/fm-editor.module';
+import {StaffCommonModule} from '@eg/staff/common.module';
+import {HoldsModule} from '@eg/staff/share/holds/holds.module';
+import {HoldingsModule} from '@eg/staff/share/holdings/holdings.module';
+import {BookingModule} from '@eg/staff/share/booking/booking.module';
+import {PatronModule} from '@eg/staff/share/patron/patron.module';
+import {HoldsUiRoutingModule} from './routing.module';
+import {HoldsPullListComponent} from './pull-list.component';
+
+@NgModule({
+  declarations: [
+    HoldsPullListComponent
+  ],
+  imports: [
+    StaffCommonModule,
+    FmRecordEditorModule,
+    HoldsModule,
+    HoldingsModule,
+    BookingModule,
+    PatronModule,
+    HoldsUiRoutingModule
+  ],
+  providers: [
+  ]
+})
+
+export class HoldsUiModule {}
diff --git a/Open-ILS/src/eg2/src/app/staff/circ/holds/pull-list.component.html b/Open-ILS/src/eg2/src/app/staff/circ/holds/pull-list.component.html
new file mode 100644 (file)
index 0000000..263463a
--- /dev/null
@@ -0,0 +1,11 @@
+
+<eg-staff-banner i18n-bannerText bannerText="Holds Pull List"></eg-staff-banner>
+
+<eg-holds-grid
+  persistKey="circ.holds.pull_list"
+  printTemplate="hold_pull_list"
+  [enablePreFetch]="true"
+  [hidePickupLibFilter]="true"
+  [pullListOrg]="targetOrg()">
+</eg-holds-grid>
+
diff --git a/Open-ILS/src/eg2/src/app/staff/circ/holds/pull-list.component.ts b/Open-ILS/src/eg2/src/app/staff/circ/holds/pull-list.component.ts
new file mode 100644 (file)
index 0000000..ee3e696
--- /dev/null
@@ -0,0 +1,30 @@
+import {Component, OnInit, Input, ViewChild, HostListener} from '@angular/core';
+import {NgbTabset, NgbTabChangeEvent} from '@ng-bootstrap/ng-bootstrap';
+import {Router, ActivatedRoute, ParamMap} from '@angular/router';
+import {PcrudService} from '@eg/core/pcrud.service';
+import {IdlObject} from '@eg/core/idl.service';
+import {AuthService} from '@eg/core/auth.service';
+import {StoreService} from '@eg/core/store.service';
+
+@Component({
+  selector: 'eg-holds-pull-list',
+  templateUrl: 'pull-list.component.html'
+})
+export class HoldsPullListComponent implements OnInit {
+
+    constructor(
+        private router: Router,
+        private route: ActivatedRoute,
+        private pcrud: PcrudService,
+        private auth: AuthService,
+        private store: StoreService
+    ) {}
+
+    ngOnInit() {
+    }
+
+    targetOrg(): number {
+        return this.auth.user().ws_ou();
+    }
+}
+
diff --git a/Open-ILS/src/eg2/src/app/staff/circ/holds/routing.module.ts b/Open-ILS/src/eg2/src/app/staff/circ/holds/routing.module.ts
new file mode 100644 (file)
index 0000000..960eaee
--- /dev/null
@@ -0,0 +1,15 @@
+import {NgModule} from '@angular/core';
+import {RouterModule, Routes} from '@angular/router';
+import {HoldsPullListComponent} from './pull-list.component';
+
+const routes: Routes = [{
+  path: 'pull-list',
+  component: HoldsPullListComponent
+}];
+
+@NgModule({
+  imports: [RouterModule.forChild(routes)],
+  exports: [RouterModule]
+})
+
+export class HoldsUiRoutingModule {}
index 49cd79a..0e94cc5 100644 (file)
@@ -1,16 +1,19 @@
 import {NgModule} from '@angular/core';
 import {RouterModule, Routes} from '@angular/router';
 
-const routes: Routes = [
-  { path: 'patron',
-    loadChildren: () =>
-      import('./patron/routing.module').then(m => m.CircPatronRoutingModule)
-  },
-  { path: 'item',
-    loadChildren: () =>
-      import('./item/routing.module').then(m => m.CircItemRoutingModule)
-  }
-];
+const routes: Routes = [{
+  path: 'patron',
+  loadChildren: () =>
+    import('./patron/routing.module').then(m => m.CircPatronRoutingModule)
+}, {
+  path: 'item',
+  loadChildren: () =>
+    import('./item/routing.module').then(m => m.CircItemRoutingModule)
+}, {
+  path: 'holds',
+  loadChildren: () =>
+    import('./holds/holds.module').then(m => m.HoldsUiModule)
+}];
 
 @NgModule({
   imports: [RouterModule.forChild(routes)],
index bd57834..6040ccf 100644 (file)
@@ -61,7 +61,7 @@
             <span class="material-icons" aria-hidden="true">pin_drop</span>
             <span i18n>Capture Holds</span>
           </a>
-          <a class="dropdown-item" href="/eg/staff/circ/holds/pull">
+          <a class="dropdown-item" routerLink="/staff/circ/holds/pull-list">
             <span class="material-icons" aria-hidden="true">view_list</span>
             <span i18n>Pull List for Hold Requests</span>
           </a>
index f0dce35..524868c 100644 (file)
@@ -33,6 +33,8 @@
 
   <ng-container *ngIf="mode == 'list' && initComplete()">
 
+    <h3 i18n>Holds Count: {{holdsCount}}</h3>
+
     <div class="row" *ngIf="!hidePickupLibFilter">
       <div class="col-lg-4">
         <div class="input-group">
       </div>
     </div>
 
+    <div class="row" *ngIf="pullListOrg">
+      <div class="col-lg-4 mb-2">
+        <div class="input-group">
+          <div class="input-group-prepend">
+            <div class="input-group-text" i18n>View Pull List For:</div>
+          </div>
+          <eg-org-select [initialOrgId]="pullListOrg"
+            (onChange)="pullListOrgChanged($event)">
+          </eg-org-select>
+        </div>
+      </div>
+    </div>
+
     <eg-grid #holdsGrid [dataSource]="gridDataSource" [sortable]="true"
       [useLocalSort]="enablePreFetch" [cellTextGenerator]="cellTextGenerator"
       [showFields]="showFields"
       [multiSortable]="true" [persistKey]="persistKey"
       (onRowActivate)="showDetail($event)">
 
-      <eg-grid-toolbar-checkbox (onChange)="preFetchHolds($event)" *ngIf="!hopeless"
+      <eg-grid-toolbar-button *ngIf="pullListOrg" 
+        (onClick)="printHolds()" i18n-label label="Print Full List">
+      </eg-grid-toolbar-button>
+
+      <eg-grid-toolbar-checkbox
+        (onChange)="preFetchHolds($event)" *ngIf="!hopeless && preFetchSetting"
         [initialValue]="enablePreFetch" i18n-label label="Pre-Fetch All Holds">
       </eg-grid-toolbar-checkbox>
 
index 3a95b05..06247c1 100644 (file)
@@ -33,6 +33,9 @@ export class HoldsGridComponent implements OnInit {
     @Input() initialPickupLib: number | IdlObject;
     @Input() hidePickupLibFilter: boolean;
 
+    // Setting a value here puts us into "pull list" mode.
+    @Input() pullListOrg: number;
+
     // If true, only retrieve holds with a Hopeless Date
     // and enable related Actions
     @Input() hopeless: boolean;
@@ -47,7 +50,7 @@ export class HoldsGridComponent implements OnInit {
     // If set, all holds are fetched on grid load and sorting/paging all
     // happens in the client.  If false, sorting and paging occur on
     // the server.
-    enablePreFetch: boolean;
+    @Input() enablePreFetch: boolean;
 
     // How to sort when no sort parameters have been applied
     // via grid controls.  This uses the eg-grid sort format:
@@ -200,6 +203,11 @@ export class HoldsGridComponent implements OnInit {
         this.holdsGrid.reload();
     }
 
+    pullListOrgChanged(org: IdlObject) {
+        this.pullListOrg = org.id();
+        this.holdsGrid.reload();
+    }
+
     preFetchHolds(apply: boolean) {
         this.enablePreFetch = apply;
 
@@ -215,13 +223,32 @@ export class HoldsGridComponent implements OnInit {
 
     applyFilters(): any {
 
-        const filters: any = {
-            is_staff_request: true,
-            fulfillment_time: this._showFulfilledSince ?
-                this._showFulfilledSince.toISOString() : null,
-            cancel_time: this._showCanceledSince ?
-                this._showCanceledSince.toISOString() : null,
-        };
+        const filters: any = {};
+
+        if (this.pullListOrg) {
+            filters.cancel_time = null;
+            filters.capture_time = null;
+            filters.frozen = 'f';
+
+            // There are aliases for these (cp_status, cp_circ_lib),
+            // but the API complains when I use them.
+            filters['cp.status'] = [0, 7];
+            filters['cp.circ_lib'] = this.pullListOrg;
+
+            return filters;
+        }
+
+        if (this._showFulfilledSince) {
+            filters.fulfillment_time = this._showFulfilledSince.toISOString();
+        } else {
+            filters.fulfillment_time = null;
+        }
+
+        if (this._showCanceledSince) {
+            filters.cancel_time = this._showCanceledSince.toISOString();
+        } else {
+            filters.cancel_time = null;
+        }
 
         if (this.hopeless) {
           filters['hopeless_holds'] = {
@@ -263,7 +290,7 @@ export class HoldsGridComponent implements OnInit {
     fetchHolds(pager: Pager, sort: any[]): Observable<any> {
 
         // We need at least one filter.
-        if (!this._recordId && !this.pickupLib && !this._userId) {
+        if (!this._recordId && !this.pickupLib && !this._userId && !this.pullListOrg) {
             return of([]);
         }
 
@@ -276,6 +303,12 @@ export class HoldsGridComponent implements OnInit {
                 subObj[obj.name] = {dir: obj.dir, nulls: 'last'};
                 orderBy.push(subObj);
             });
+        } else if (this.pullListOrg) {
+            orderBy.push(
+                {copy_location_order_position: {dir: 'asc', nulls: 'last'}},
+                {acpl_name: {dir: 'asc', nulls: 'last'}},
+                {cn_label_sortkey: {dir: 'asc'}}
+            );
         }
 
         const limit = this.enablePreFetch ? null : pager.limit;
index 143584f..305f0e1 100644 (file)
@@ -21912,6 +21912,75 @@ VALUES
     'coust', 'description'),
   'array' );
 
+-- NOTE: If the template ID requires changing, beware it appears in
+-- 3 places below.
+
+INSERT INTO config.print_template 
+    (id, name, locale, active, owner, label, template) 
+VALUES (
+    4, 'hold_pull_list', 'en-US', TRUE,
+    (SELECT id FROM actor.org_unit WHERE parent_ou IS NULL),
+    oils_i18n_gettext(4, 'Hold Pull List ', 'cpt', 'label'),
+    ''
+);
+
+UPDATE config.print_template SET template = 
+$TEMPLATE$
+[%-
+    USE date;
+    SET holds = template_data;
+    # template_data is an arry of wide_hold hashes.
+-%]
+<div>
+  <style>
+    #holds-pull-list-table td { 
+      padding: 5px; 
+      border: 1px solid rgba(0,0,0,.05);
+    }
+  </style>
+  <table id="holds-pull-list-table">
+    <thead>
+      <tr>
+        <th>Type</th>
+        <th>Title</th>
+        <th>Author</th>
+        <th>Shelf Location</th>
+        <th>Call Number</th>
+        <th>Barcode/Part</th>
+      </tr>
+    </thead>
+    <tbody>
+      [% FOR hold IN holds %]
+      <tr>
+        <td>[% hold.hold_type %]</td>
+        <td style="width: 30%">[% hold.title %]</td>
+        <td style="width: 25%">[% hold.author %]</td>
+        <td>[% hold.acpl_name %]</td>
+        <td>[% hold.cn_full_label %]</td>
+        <td>[% hold.cp_barcode %][% IF hold.p_label %]/[% hold.p_label %][% END %]</td>
+      </tr>
+      [% END %]
+    </tbody>
+  </table>
+</div>
+$TEMPLATE$ WHERE id = 4;
+
+INSERT INTO config.workstation_setting_type (name, grp, datatype, label)
+VALUES (
+    'eg.grid.circ.holds.pull_list', 'gui', 'object', 
+    oils_i18n_gettext(
+        'circ.holds.pull_list',
+        'Hold Pull List Grid Settings',
+        'cwst', 'label'
+    )
+), (
+    'circ.holds.pull_list.prefetch', 'gui', 'bool', 
+    oils_i18n_gettext(
+        'circ.holds.pull_list.prefetch',
+        'Hold Pull List Prefetch Preference',
+        'cwst', 'label'
+    )
+);
 
 INSERT INTO config.workstation_setting_type (name, grp, datatype, label)
 VALUES (
diff --git a/Open-ILS/src/sql/Pg/upgrade/XXXX.data.pull-list-print-template.sql b/Open-ILS/src/sql/Pg/upgrade/XXXX.data.pull-list-print-template.sql
new file mode 100644 (file)
index 0000000..6b1c71c
--- /dev/null
@@ -0,0 +1,77 @@
+
+BEGIN;
+
+-- SELECT evergreen.upgrade_deps_block_check('TODO', :eg_version);
+
+-- NOTE: If the template ID requires changing, beware it appears in
+-- 3 places below.
+
+INSERT INTO config.print_template 
+    (id, name, locale, active, owner, label, template) 
+VALUES (
+    4, 'hold_pull_list', 'en-US', TRUE,
+    (SELECT id FROM actor.org_unit WHERE parent_ou IS NULL),
+    oils_i18n_gettext(4, 'Hold Pull List ', 'cpt', 'label'),
+    ''
+);
+
+UPDATE config.print_template SET template = 
+$TEMPLATE$
+[%-
+    USE date;
+    SET holds = template_data;
+    # template_data is an arry of wide_hold hashes.
+-%]
+<div>
+  <style>
+    #holds-pull-list-table td { 
+      padding: 5px; 
+      border: 1px solid rgba(0,0,0,.05);
+    }
+  </style>
+  <table id="holds-pull-list-table">
+    <thead>
+      <tr>
+        <th>Type</th>
+        <th>Title</th>
+        <th>Author</th>
+        <th>Shelf Location</th>
+        <th>Call Number</th>
+        <th>Barcode/Part</th>
+      </tr>
+    </thead>
+    <tbody>
+      [% FOR hold IN holds %]
+      <tr>
+        <td>[% hold.hold_type %]</td>
+        <td style="width: 30%">[% hold.title %]</td>
+        <td style="width: 25%">[% hold.author %]</td>
+        <td>[% hold.acpl_name %]</td>
+        <td>[% hold.cn_full_label %]</td>
+        <td>[% hold.cp_barcode %][% IF hold.p_label %]/[% hold.p_label %][% END %]</td>
+      </tr>
+      [% END %]
+    </tbody>
+  </table>
+</div>
+$TEMPLATE$ WHERE id = 4;
+
+INSERT INTO config.workstation_setting_type (name, grp, datatype, label)
+VALUES (
+    'eg.grid.circ.holds.pull_list', 'gui', 'object', 
+    oils_i18n_gettext(
+        'circ.holds.pull_list',
+        'Hold Pull List Grid Settings',
+        'cwst', 'label'
+    )
+), (
+    'circ.holds.pull_list.prefetch', 'gui', 'bool', 
+    oils_i18n_gettext(
+        'circ.holds.pull_list.prefetch',
+        'Hold Pull List Prefetch Preference',
+        'cwst', 'label'
+    )
+);
+
+COMMIT;
+
index 5daf52d..bb0409a 100644 (file)
             </a>
           </li>
           <li>
-            <a href="./circ/holds/pull" target="_self">
+            <a href="/eg2/staff/circ/holds/pull-list">
               <span class="glyphicon glyphicon-th-list" aria-hidden="true"></span>
               [% l('Pull List for Hold Requests') %]
             </a>