LP1904036 Patron group details UI
authorBill Erickson <berickxx@gmail.com>
Tue, 16 Mar 2021 16:10:34 +0000 (12:10 -0400)
committerGalen Charlton <gmc@equinoxOLI.org>
Fri, 28 Oct 2022 00:13:27 +0000 (20:13 -0400)
Signed-off-by: Bill Erickson <berickxx@gmail.com>
Signed-off-by: Jane Sandberg <js7389@princeton.edu>
Signed-off-by: Galen Charlton <gmc@equinoxOLI.org>
Open-ILS/src/eg2/src/app/staff/circ/patron/group.component.html [new file with mode: 0644]
Open-ILS/src/eg2/src/app/staff/circ/patron/group.component.ts [new file with mode: 0644]
Open-ILS/src/eg2/src/app/staff/circ/patron/patron.component.html
Open-ILS/src/eg2/src/app/staff/circ/patron/patron.module.ts
Open-ILS/src/eg2/src/app/staff/circ/patron/patron.service.ts
Open-ILS/src/eg2/src/app/staff/circ/patron/summary.component.html

diff --git a/Open-ILS/src/eg2/src/app/staff/circ/patron/group.component.html b/Open-ILS/src/eg2/src/app/staff/circ/patron/group.component.html
new file mode 100644 (file)
index 0000000..73ceeea
--- /dev/null
@@ -0,0 +1,46 @@
+<eg-prompt-dialog #moveToGroupDialog
+  i18n-dialogBody dialogBody="Enter the patron barcode">
+</eg-prompt-dialog>
+<eg-alert-dialog #userNotFoundDialog
+  i18n-dialogBody dialogBody="User not found">
+</eg-alert-dialog>
+
+<h3 i18n>Group Member Details</h3>
+
+<div class="row">
+  <div class="col-lg-12">
+    <span i18n>Total Owed:</span>
+    <span class="ml-1">{{totalOwed | currency}}</span>
+    <span class="ml-2">Total Out:</span>
+    <span class="ml-1">{{totalOut}}</span>
+    <span class="ml-2">Total Overdue:</span>
+    <span class="ml-1">{{totalOverdue}}</span>
+  </div>
+</div>
+
+
+<div class="mt-4">
+
+  <eg-grid idlClass="au" #groupGrid
+    [dataSource]="dataSource" [sortable]="true" [useLocalSort]="true"
+    showFields="active,barred,dob,family_name,first_given_name,
+      master_account,second_given_name,balance_owed,total_out,overdue">
+
+    <eg-grid-toolbar-button label="Move Another Patron To This Group" 
+      i18n-label (onClick)="movePatronToGroup()">
+    </eg-grid-toolbar-button>
+
+    <eg-grid-column name="balance_owed" path="_stats.fines.balance_owed" 
+      datatype="currency" label="Balance Owed" i18n-label>
+    </eg-grid-column>
+    <eg-grid-column name="total_out" path="_stats.checkouts.total_out" 
+      label="Items Out" i18n-label>
+    </eg-grid-column>
+    <eg-grid-column name="overdue" path="_stats.checkouts.overdue" 
+      label="Items Overdue" i18n-label>
+    </eg-grid-column>
+
+  </eg-grid>
+
+</div>
+
diff --git a/Open-ILS/src/eg2/src/app/staff/circ/patron/group.component.ts b/Open-ILS/src/eg2/src/app/staff/circ/patron/group.component.ts
new file mode 100644 (file)
index 0000000..af69cc6
--- /dev/null
@@ -0,0 +1,125 @@
+import {Component, Input, OnInit, AfterViewInit, ViewChild} from '@angular/core';
+import {Router, ActivatedRoute, ParamMap} from '@angular/router';
+import {from, empty, range} from 'rxjs';
+import {concatMap, tap, takeLast} from 'rxjs/operators';
+import {NgbNav, NgbNavChangeEvent} from '@ng-bootstrap/ng-bootstrap';
+import {IdlObject} from '@eg/core/idl.service';
+import {EventService} from '@eg/core/event.service';
+import {OrgService} from '@eg/core/org.service';
+import {NetService} from '@eg/core/net.service';
+import {PcrudService, PcrudContext} from '@eg/core/pcrud.service';
+import {AuthService} from '@eg/core/auth.service';
+import {PatronService} from '@eg/staff/share/patron/patron.service';
+import {PatronContextService} from './patron.service';
+import {GridDataSource, GridColumn, GridCellTextGenerator} from '@eg/share/grid/grid';
+import {GridComponent} from '@eg/share/grid/grid.component';
+import {Pager} from '@eg/share/util/pager';
+import {PromptDialogComponent} from '@eg/share/dialog/prompt.component';
+import {AlertDialogComponent} from '@eg/share/dialog/alert.component';
+import {ConfirmDialogComponent} from '@eg/share/dialog/confirm.component';
+
+@Component({
+  templateUrl: 'group.component.html',
+  selector: 'eg-patron-group'
+})
+export class PatronGroupComponent implements OnInit {
+
+    @Input() patronId: number;
+    patrons: IdlObject[] = [];
+    totalOwed = 0;
+    totalOut = 0;
+    totalOverdue = 0;
+    usergroup: number;
+
+    dataSource: GridDataSource = new GridDataSource();
+    @ViewChild('groupGrid') private groupGrid: GridComponent;
+    @ViewChild('moveToGroupDialog') private moveToGroupDialog: PromptDialogComponent;
+    @ViewChild('userNotFoundDialog') private userNotFoundDialog: AlertDialogComponent;
+
+    constructor(
+        private router: Router,
+        private evt: EventService,
+        private net: NetService,
+        private auth: AuthService,
+        private org: OrgService,
+        private pcrud: PcrudService,
+        private patronService: PatronService,
+        private context: PatronContextService
+    ) {}
+
+    ngOnInit() {
+
+        this.dataSource.getRows = (pager: Pager, sort: any[]) =>
+            from(this.patrons.slice(pager.offset, pager.offset + pager.limit));
+
+        if (this.context.patron) {
+            this.getGroupUsers(this.context.patron.usrgroup());
+
+        } else {
+            this.patronService.getById(this.patronId)
+            .then(patron => this.getGroupUsers(patron.usrgroup()));
+        }
+    }
+
+    getGroupUsers(usergroup: number) {
+        this.usergroup = usergroup;
+        this.patrons = [];
+
+        this.pcrud.search('au',
+            {usrgroup: usergroup, deleted: 'f'}, {authoritative: true})
+        .pipe(concatMap(u => {
+
+            const promise = this.context.getPatronVitalStats(u.id())
+            .then(stats => {
+                this.totalOwed += stats.fines.balance_owed;
+                this.totalOut += stats.checkouts.total_out;
+                this.totalOverdue += stats.checkouts.overdue;
+                u._stats = stats;
+                this.patrons.push(u);
+            });
+
+            return from(promise);
+
+        })).subscribe(null, null, () => this.groupGrid.reload());
+    }
+
+    movePatronToGroup() {
+
+        this.moveToGroupDialog.open().subscribe(barcode => {
+            if (!barcode) { return null; }
+
+            this.patronService.getByBarcode(barcode)
+            .then(resp => {
+                if (resp === null) {
+                    this.userNotFoundDialog.open();
+                    return null;
+                }
+
+                resp.usrgroup(this.usergroup);
+                resp.ischanged(true);
+
+                return this.net.request(
+                    'open-ils.actor',
+                    'open-ils.actor.patron.update',
+                    this.auth.token(), resp
+                ).toPromise();
+            })
+            .then(resp => {
+                if (resp === null) { return null; }
+
+                const evt = this.evt.parse(resp);
+                if (evt) {
+                    console.error(evt);
+                    alert(evt);
+                    return null;
+                }
+
+                return this.getGroupUsers(this.usergroup);
+            })
+            .then(resp => {
+                if (resp === null) { return null; }
+                this.groupGrid.reload();
+            });
+        });
+    }
+}
index d13a72f..dc63f84 100644 (file)
                 <eg-patron-statcats [patronId]="patronId">
                 </eg-patron-statcats>
               </div>
+              <div *ngSwitchCase="'group'">
+                <eg-patron-group [patronId]="patronId">
+                </eg-patron-group>
+              </div>
             </ng-container>
           </ng-template>
         </li>
index fe5f556..384726a 100644 (file)
@@ -25,6 +25,7 @@ import {BillStatementComponent} from './bill-statement.component';
 import {TestPatronPasswordComponent} from './test-password.component';
 import {PatronSurveyResponsesComponent} from './surveys.component';
 import {PatronStatCatsComponent} from './statcats.component';
+import {PatronGroupComponent} from './group.component';
 
 @NgModule({
   declarations: [
@@ -41,6 +42,7 @@ import {PatronStatCatsComponent} from './statcats.component';
     BillStatementComponent,
     TestPatronPasswordComponent,
     PatronSurveyResponsesComponent,
+    PatronGroupComponent,
     PatronStatCatsComponent
   ],
   imports: [
index 545f09a..4e9ae39 100644 (file)
@@ -135,12 +135,7 @@ export class PatronContextService {
         .then(_ => this.compileAlerts());
     }
 
-    getPatronStats(id: number): Promise<any> {
-
-        // When quickly navigating patron search results it's possible
-        // for this.patron to be cleared right before this function
-        // is called.  Exit early instead of making an unneeded call.
-        if (!this.patron) { return Promise.resolve(); }
+    getPatronVitalStats(id: number): Promise<PatronStats> {
 
         return this.net.request(
             'open-ils.actor',
@@ -166,9 +161,22 @@ export class PatronContextService {
                 stats.checkouts.total_out += stats.checkouts.lost;
             }
 
-            this.patronStats = stats;
+            return stats;
+        });
+    }
+
+    getPatronStats(id: number): Promise<any> {
+
+        // When quickly navigating patron search results it's possible
+        // for this.patron to be cleared right before this function
+        // is called.  Exit early instead of making an unneeded call.
+        if (!this.patron) { return Promise.resolve(); }
+
+        return this.getPatronVitalStats(id)
+
+        .then(stats => this.patronStats = stats)
 
-        }).then(_ => {
+        .then(_ => {
 
             if (!this.patron) { return; }
 
index 5c2eb17..df7435f 100644 (file)
@@ -87,7 +87,7 @@
   <ng-container *ngIf="context.patronStats">
 
     <div class="row mb-1"
-      [ngClass]="{'alert alert-danger p-0': context.patronStats.fines.total_owed > 0}">
+      [ngClass]="{'alert alert-danger p-0': context.patronStats.fines.balance_owed > 0}">
       <div class="col-lg-5" i18n>Fines Owed</div>
       <div class="col-lg-7">{{context.patronStats.fines.balance_owed | currency}}</div>
     </div>