LP1904036 structural stuff; message actions
authorBill Erickson <berickxx@gmail.com>
Tue, 6 Apr 2021 15:26:48 +0000 (11:26 -0400)
committerGalen Charlton <gmc@equinoxOLI.org>
Fri, 28 Oct 2022 00:13:29 +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>
20 files changed:
Open-ILS/src/eg2/src/app/staff/circ/patron/alerts.component.html
Open-ILS/src/eg2/src/app/staff/circ/patron/alerts.component.ts
Open-ILS/src/eg2/src/app/staff/circ/patron/bills.component.ts
Open-ILS/src/eg2/src/app/staff/circ/patron/checkout.component.ts
Open-ILS/src/eg2/src/app/staff/circ/patron/edit.component.ts
Open-ILS/src/eg2/src/app/staff/circ/patron/group.component.ts
Open-ILS/src/eg2/src/app/staff/circ/patron/holds.component.html
Open-ILS/src/eg2/src/app/staff/circ/patron/holds.component.ts
Open-ILS/src/eg2/src/app/staff/circ/patron/items.component.html
Open-ILS/src/eg2/src/app/staff/circ/patron/messages.component.html
Open-ILS/src/eg2/src/app/staff/circ/patron/messages.component.ts
Open-ILS/src/eg2/src/app/staff/circ/patron/patron.component.html
Open-ILS/src/eg2/src/app/staff/circ/patron/patron.component.ts
Open-ILS/src/eg2/src/app/staff/circ/patron/patron.service.ts
Open-ILS/src/eg2/src/app/staff/share/patron/merge-dialog.component.html
Open-ILS/src/eg2/src/app/staff/share/patron/merge-dialog.component.ts
Open-ILS/src/eg2/src/app/staff/share/patron/patron.service.ts
Open-ILS/src/eg2/src/app/staff/share/patron/penalty-dialog.component.ts
Open-ILS/src/eg2/src/app/staff/share/patron/summary.component.html
Open-ILS/src/eg2/src/app/staff/share/patron/summary.component.ts

index 6b869cb..6c88fba 100644 (file)
@@ -1,55 +1,55 @@
 
-<div *ngIf="context.alerts">
+<div *ngIf="alerts()">
 
   <img class="mt-n4" src="/images/stop_sign.png"/>
 
-  <div class="alert alert-info" *ngIf="context.alerts.holdsReady > 0" i18n>
-    Holds available: {{context.alerts.holdsReady}}
+  <div class="alert alert-info" *ngIf="alerts().holdsReady > 0" i18n>
+    Holds available: {{alerts().holdsReady}}
   </div>
 
-  <div class="mt-2 alert alert-warning" *ngIf="context.alerts.accountExpired" i18n>
+  <div class="mt-2 alert alert-warning" *ngIf="alerts().accountExpired" i18n>
     Patron account is EXPIRED.
   </div>
 
-  <div class="mt-2 alert alert-warning" *ngIf="context.alerts.accountExpiresSoon" i18n>
+  <div class="mt-2 alert alert-warning" *ngIf="alerts().accountExpiresSoon" i18n>
     Patron account will expire soon.  Please renew.
   </div>
 
-  <div class="mt-2 alert alert-danger" *ngIf="context.alerts.patronBarred" i18n>
+  <div class="mt-2 alert alert-danger" *ngIf="alerts().patronBarred" i18n>
     Patron account is BARRED
   </div>
 
-  <div class="mt-2 alert alert-warning" *ngIf="context.alerts.patronInactive" i18n>
+  <div class="mt-2 alert alert-warning" *ngIf="alerts().patronInactive" i18n>
     Patron account is INACTIVE
   </div>
 
-  <div class="mt-2 alert alert-warning" *ngIf="context.alerts.retrievedWithInactive" i18n>
+  <div class="mt-2 alert alert-warning" *ngIf="alerts().retrievedWithInactive" i18n>
     Patron account retrieved with an INACTIVE card.
   </div>
 
-  <div class="mt-2 alert alert-warning" *ngIf="context.alerts.invalidAddress" i18n>
+  <div class="mt-2 alert alert-warning" *ngIf="alerts().invalidAddress" i18n>
     Patron account has invalid addresses.
   </div>
 
   <!-- alert message -->
-  <div class="row" *ngIf="context.alerts.alertMessage">
+  <div class="row" *ngIf="alerts().alertMessage">
     <div class="col-lg-6 offset-lg-3">
       <div class="card">
         <div class="card-header" i18n>Alert Message</div>
-        <div class="card-body">{{context.alerts.alertMessage}}</div>
+        <div class="card-body">{{alerts().alertMessage}}</div>
       </div>
     </div>
   </div>
 
   <!-- penalties -->
-  <div class="row" *ngIf="context.alerts.alertPenalties.length">
+  <div class="row" *ngIf="alerts().alertPenalties.length">
     <div class="col-lg-12">
       <div class="card">
         <div class="card-header" i18n>Penalties</div>
         <div class="card-body">
           <ul class="list-group list-group-flush">
             <li class="list-group-item" 
-              *ngFor="let penalty of context.alerts.alertPenalties">
+              *ngFor="let penalty of alerts().alertPenalties">
               <div class="row">
                 <div class="col-lg-2">
                   {{context.orgSn(penalty.org_unit())}}
index 814bdca..ec52008 100644 (file)
@@ -3,7 +3,7 @@ import {Router, ActivatedRoute, ParamMap} from '@angular/router';
 import {NgbNav, NgbNavChangeEvent} from '@ng-bootstrap/ng-bootstrap';
 import {OrgService} from '@eg/core/org.service';
 import {NetService} from '@eg/core/net.service';
-import {PatronService} from '@eg/staff/share/patron/patron.service';
+import {PatronService, PatronAlerts} from '@eg/staff/share/patron/patron.service';
 import {PatronContextService} from './patron.service';
 
 @Component({
@@ -21,5 +21,9 @@ export class PatronAlertsComponent implements OnInit {
 
     ngOnInit() {
     }
+
+    alerts(): PatronAlerts {
+        return this.context.summary ? this.context.summary.alerts : null;
+    }
 }
 
index 23223c0..0705c0a 100644 (file)
@@ -234,7 +234,7 @@ export class BillsComponent implements OnInit, AfterViewInit {
     }
 
     patron(): IdlObject {
-        return this.context.patron;
+        return this.context.summary ? this.context.summary.patron : null;
     }
 
     selectedPaymentInfo(): {owed: number, billed: number, paid: number} {
@@ -464,7 +464,7 @@ export class BillsComponent implements OnInit, AfterViewInit {
         }
 
         const pending = this.pendingPayment();
-        const prevBalance = this.context.patronStats.fines.balance_owed;
+        const prevBalance = this.context.summary.stats.fines.balance_owed;
         const newBalance = (prevBalance * 100 - pending * 100) / 100;
 
         const context = {
index ce696e5..0e262f3 100644 (file)
@@ -89,7 +89,7 @@ export class CheckoutComponent implements OnInit, AfterViewInit {
     collectParams(): Promise<CheckoutParams> {
 
         const params: CheckoutParams = {
-            patron_id: this.context.patron.id()
+            patron_id: this.context.summary.id
         };
 
         if (this.checkoutNoncat) {
index 805f3ab..887efa4 100644 (file)
@@ -25,6 +25,21 @@ import {AlertDialogComponent} from '@eg/share/dialog/alert.component';
 import {HoldNotifyUpdateDialogComponent} from './hold-notify-update.component';
 import {BroadcastService} from '@eg/share/util/broadcast.service';
 
+const PATRON_FLESH_FIELDS = [
+    'cards',
+    'card',
+    'groups',
+    'standing_penalties',
+    'settings',
+    'addresses',
+    'billing_address',
+    'mailing_address',
+    'stat_cat_entries',
+    'waiver_entries',
+    'usr_activity',
+    'notes'
+];
+
 const COMMON_USER_SETTING_TYPES = [
   'circ.holds_behind_desk',
   'circ.collections.exempt',
@@ -618,7 +633,7 @@ export class EditComponent implements OnInit, AfterViewInit {
 
     loadPatron(): Promise<any> {
         if (this.patronId) {
-            return this.patronService.getFleshedById(this.patronId)
+            return this.patronService.getFleshedById(this.patronId, PATRON_FLESH_FIELDS)
             .then(patron => {
                 this.patron = patron;
                 this.origUsername = patron.usrname();
@@ -1334,7 +1349,7 @@ export class EditComponent implements OnInit, AfterViewInit {
         }
 
         if (clone) {
-            this.context.patron = null;
+            this.context.summary = null;
             this.router.navigate(
                 ['/staff/circ/patron/register/clone', this.modifiedPatron.id()]);
 
index a4459c7..33435ed 100644 (file)
@@ -59,8 +59,8 @@ export class PatronGroupComponent implements OnInit {
         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());
+        if (this.context.summary) {
+            this.getGroupUsers(this.context.summary.patron.usrgroup());
 
         } else {
             this.patronService.getById(this.patronId)
@@ -137,7 +137,7 @@ export class PatronGroupComponent implements OnInit {
 
     refresh() {
         this.context.refreshPatron()
-        .then(_ => this.usergroup = this.context.patron.usrgroup())
+        .then(_ => this.usergroup = this.context.summary.patron.usrgroup())
         .then(_ => this.getGroupUsers(this.usergroup))
         .then(_ => this.groupGrid.reload());
     }
index df068e2..ecccbac 100644 (file)
@@ -13,7 +13,7 @@
         [showPlaceHoldButton]="true"
         [hidePickupLibFilter]="true"
         [defaultSort]="[{name:'request_time',dir:'asc'}]"
-        [patronId]="context.patron.id()"></eg-holds-grid>
+        [patronId]="context.summary.patron.id()"></eg-holds-grid>
     </ng-template>
   </li>
 
         [hidePickupLibFilter]="true"
         [showRecentlyCanceled]="true"
         [defaultSort]="[{name:'cancel_time',dir:'desc'}]"
-        [patronId]="context.patron.id()"></eg-holds-grid>
+        [patronId]="context.summary.patron.id()"></eg-holds-grid>
     </ng-template>
   </li>
 </ul>
 
-<ng-container *ngIf="context.patron">
+<ng-container *ngIf="context.summary">
   <div [ngbNavOutlet]="holdsNav"></div>
 </ng-container>
index 86dc486..0843880 100644 (file)
@@ -25,7 +25,7 @@ export class HoldsComponent implements OnInit {
 
     newHold() {
         this.router.navigate(['/staff/catalog/search'],
-          {queryParams: {holdForBarcode: this.context.patron.card().barcode()}});
+          {queryParams: {holdForBarcode: this.context.summary.patron.card().barcode()}});
     }
 }
 
index fe1542d..2206f06 100644 (file)
@@ -35,8 +35,8 @@
     </ng-container>
     <li ngbNavItem="noncat">
       <a ngbNavLink i18n>
-        <ng-container *ngIf="context.patronStats">
-          Non-Cataloged Circulations ({{context.patronStats.checkouts.noncat}})
+        <ng-container *ngIf="context.summary.stats">
+          Non-Cataloged Circulations ({{context.summary.stats.checkouts.noncat}})
         </ng-container>
       </a>
       <ng-template ngbNavContent>
index 88b5675..4ba910d 100644 (file)
   hideFields="id,usr,stop_date">
   <eg-grid-toolbar-button i18n-label label="Apply Penalty / Message"
     (onClick)="applyPenalty()">
+  <eg-grid-toolbar-action (onClick)="archive($event)"
+    label="Archive Selected" i18n-label></eg-grid-toolbar-action>
+  <eg-grid-toolbar-action (onClick)="remove($event)"
+    label="Delete Selected" i18n-label></eg-grid-toolbar-action>
+  <eg-grid-toolbar-action (onClick)="modify($event)"
+    label="Modify Selected" i18n-label></eg-grid-toolbar-action>
   </eg-grid-toolbar-button>
   <eg-grid-column path="standing_penalty.label">
   </eg-grid-column>
index 352ff39..8680f64 100644 (file)
@@ -1,5 +1,7 @@
 import {Component, ViewChild, OnInit, Input, AfterViewInit} from '@angular/core';
-import {empty} from 'rxjs';
+import {empty, from} from 'rxjs';
+import {concatMap, tap} from 'rxjs/operators';
+import {IdlObject} from '@eg/core/idl.service';
 import {NetService} from '@eg/core/net.service';
 import {OrgService} from '@eg/core/org.service';
 import {PcrudService} from '@eg/core/pcrud.service';
@@ -77,7 +79,7 @@ export class PatronMessagesComponent implements OnInit {
             };
 
             flesh.order_by = orderBy;
-            return this.pcrud.search('ausp', query, flesh);
+            return this.pcrud.search('ausp', query, flesh, {authoritative: true});
         }
 
         this.archiveDataSource.getRows = (pager: Pager, sort: any[]) => {
@@ -95,7 +97,7 @@ export class PatronMessagesComponent implements OnInit {
 
             flesh.order_by = orderBy;
 
-            return this.pcrud.search('ausp', query, flesh);
+            return this.pcrud.search('ausp', query, flesh, {authoritative: true});
         }
     }
 
@@ -123,6 +125,38 @@ export class PatronMessagesComponent implements OnInit {
             if (changes) { this.mainGrid.reload(); }
         });
     }
+
+    archive(penalties: IdlObject[]) {
+        penalties.forEach(p => p.stop_date('now'));
+        this.pcrud.update(penalties).toPromise()
+        .then(_ => {
+            this.mainGrid.reload();
+            this.archiveGrid.reload();
+        });
+    }
+
+    remove(penalties: IdlObject[]) {
+        this.pcrud.remove(penalties).toPromise()
+        .then(_ => {
+            this.mainGrid.reload();
+            this.archiveGrid.reload();
+        });
+    }
+
+    modify(penalties: IdlObject[]) {
+        let modified = false;
+        from(penalties).pipe(concatMap(penalty => {
+            this.penaltyDialog.penalty = penalty;
+            return this.penaltyDialog.open().pipe(tap(changed => {
+                if (changed) { modified = true; }
+            }));
+        })).toPromise().then(_ => {
+            if (modified) {
+                this.mainGrid.reload();
+                this.archiveGrid.reload();
+            }
+        });
+    }
 }
 
 
index bab79c3..71a2e78 100644 (file)
@@ -1,14 +1,14 @@
 
-<ng-container *ngIf="!context.patron">
+<ng-container *ngIf="!context.summary">
   <eg-staff-banner bannerText="Manage Patrons" i18n-bannerText>
   </eg-staff-banner>
 </ng-container>
 
-<ng-container *ngIf="context.patron">
+<ng-container *ngIf="context.summary">
   <eg-staff-banner i18n-bannerText bannerText="Patron: 
-    {{context.patron.family_name()}}, 
-    {{context.patron.first_given_name()}} 
-    {{context.patron.second_given_name()}}">
+    {{context.summary.patron.family_name()}}, 
+    {{context.summary.patron.first_given_name()}} 
+    {{context.summary.patron.second_given_name()}}">
   </eg-staff-banner>
 </ng-container>
 
   <ng-container *ngIf="showSummaryPane()">
     <div class="col-lg-3">
       <div class="sticky-top-with-nav bg-white">
-        <ng-container *ngIf="context.patron">
-          <eg-patron-summary [patron]="context.patron"
-            [stats]="context.patronStats" [alerts]="context.alerts">
-          </eg-patron-summary>
+        <ng-container *ngIf="context.summary">
+          <eg-patron-summary [summary]="context.summary"></eg-patron-summary>
         </ng-container>
       </div>
     </div>
@@ -48,7 +46,7 @@
           </li>
         </ng-container>
 
-        <li ngbNavItem="checkout" [disabled]="!context.patron">
+        <li ngbNavItem="checkout" [disabled]="!context.summary">
           <a ngbNavLink i18n>Check Out</a>
           <ng-template ngbNavContent>
             <div class="">
@@ -57,7 +55,7 @@
           </ng-template>
         </li>
 
-        <li ngbNavItem="items_out" [disabled]="!context.patron">
+        <li ngbNavItem="items_out" [disabled]="!context.summary">
           <a ngbNavLink i18n>
             Items Out ({{counts('checkouts', 'total_out')}})
           </a>
@@ -68,7 +66,7 @@
           </ng-template>
         </li>
 
-        <li ngbNavItem="holds" [disabled]="!context.patron">
+        <li ngbNavItem="holds" [disabled]="!context.summary">
           <a ngbNavLink i18n>
             Holds ({{counts('holds', 'ready')}} / {{counts('holds', 'total')}})
           </a>
@@ -77,7 +75,7 @@
           </ng-template>
         </li>
 
-        <li ngbNavItem="bills" [disabled]="!context.patron">
+        <li ngbNavItem="bills" [disabled]="!context.summary">
           <a ngbNavLink (click)="billsTabClicked()" i18n>
             Bills 
             <span [ngClass]="{'text-danger': counts('fines', 'balance_owed') > 0}">
@@ -95,7 +93,7 @@
           </ng-template>
         </li>
 
-        <li ngbNavItem="messages" [disabled]="!context.patron">
+        <li ngbNavItem="messages" [disabled]="!context.summary">
           <a ngbNavLink i18n>Messages</a>
           <ng-template ngbNavContent>
             <div class="">
           </ng-template>
         </li>
 
-        <li ngbNavItem="edit" [disabled]="!context.patron">
+        <li ngbNavItem="edit" [disabled]="!context.summary">
           <a ngbNavLink i18n>Edit</a>
           <ng-template ngbNavContent>
             <eg-patron-edit [patronId]="patronId" [toolbar]="editorToolbar">
           </ng-template>
         </li>
 
-        <li ngbDropdown ngbNavItem="other" [disabled]="!context.patron">
-          <a [attr.href]="context.patron ? '' : null" 
+        <li ngbDropdown ngbNavItem="other" [disabled]="!context.summary">
+          <a [attr.href]="context.summary ? '' : null" 
             (click)="false" class="nav-link" ngbDropdownToggle>Other</a>
           <div ngbDropdownMenu>
             <a routerLink="/staff/circ/patron/{{patronId}}/alerts" 
index d37e618..ac1b084 100644 (file)
@@ -75,7 +75,7 @@ export class PatronComponent implements OnInit, AfterViewInit {
             }
 
             const prevId =
-                this.context.patron ? this.context.patron.id() : null;
+                this.context.summary ? this.context.summary.id : null;
 
             if (this.patronId) {
 
@@ -159,8 +159,8 @@ export class PatronComponent implements OnInit, AfterViewInit {
 
     routeToAlertsPane() {
         if (this.patronTab !== 'search' &&
-            this.context.patron &&
-            this.context.alerts.hasAlerts() &&
+            this.context.summary &&
+            this.context.summary.alerts.hasAlerts() &&
             !this.context.patronAlertsShown()) {
 
            this.router.navigate(['/staff/circ/patron', this.patronId, 'alerts']);
@@ -183,8 +183,8 @@ export class PatronComponent implements OnInit, AfterViewInit {
 
     disablePurge(): boolean {
         return (
-            !this.context.patron ||
-            this.context.patron.super_user() === 't' ||
+            !this.context.summary ||
+            this.context.summary.patron.super_user() === 't' ||
             this.patronId === this.auth.user().id()
         );
     }
@@ -195,8 +195,8 @@ export class PatronComponent implements OnInit, AfterViewInit {
     }
 
     counts(part: string, field: string): number {
-        if (this.context && this.context.patronStats) {
-            return this.context.patronStats[part][field];
+        if (this.context.summary && this.context.summary.stats) {
+            return this.context.summary.stats[part][field];
         } else {
             return 0;
         }
index e25a05b..c7948e0 100644 (file)
@@ -3,7 +3,7 @@ import {IdlObject} from '@eg/core/idl.service';
 import {NetService} from '@eg/core/net.service';
 import {OrgService} from '@eg/core/org.service';
 import {AuthService} from '@eg/core/auth.service';
-import {PatronService, PatronStats, PatronAlerts
+import {PatronService, PatronSummary, PatronStats, PatronAlerts
     } from '@eg/staff/share/patron/patron.service';
 import {PatronSearch} from '@eg/staff/share/patron/search.component';
 import {StoreService} from '@eg/core/store.service';
@@ -47,12 +47,8 @@ const PATRON_FLESH_FIELDS = [
 @Injectable()
 export class PatronContextService {
 
-    patron: IdlObject;
-    patronStats: PatronStats;
-    alerts: PatronAlerts;
-
+    summary: PatronSummary;
     loaded = false;
-
     lastPatronSearch: PatronSearch;
     searchBarcode: string = null;
 
@@ -76,12 +72,17 @@ export class PatronContextService {
 
     // Update the patron data without resetting all of the context data.
     refreshPatron(id?: number): Promise<any> {
-        if (!id) { id = this.patron.id(); }
 
-        this.alerts = new PatronAlerts();
+        if (!id) {
+            if (!this.summary) {
+                return Promise.reject('no can do');
+            } else {
+                id = this.summary.id;
+            }
+        }
 
         return this.patrons.getFleshedById(id, PATRON_FLESH_FIELDS)
-        .then(p => this.patron = p)
+        .then(p => this.summary = new PatronSummary(p))
         .then(_ => this.getPatronStats(id))
         .then(_ => this.compileAlerts());
     }
@@ -91,33 +92,34 @@ export class PatronContextService {
         // 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(); }
+        if (!this.summary) { return Promise.resolve(); }
 
-        return this.patrons.getVitalStats(this.patron)
-        .then(stats => this.patronStats = stats);
+        return this.patrons.getVitalStats(this.summary.patron)
+        .then(stats => this.summary.stats = stats);
     }
 
     patronAlertsShown(): boolean {
-        if (!this.patron) { return false; }
+        if (!this.summary) { return false; }
         const shown = this.store.getSessionItem('eg.circ.last_alerted_patron');
-        if (shown === this.patron.id()) { return true; }
-        this.store.setSessionItem('eg.circ.last_alerted_patron', this.patron.id());
+        if (shown === this.summary.patron.id()) { return true; }
+        this.store.setSessionItem('eg.circ.last_alerted_patron', this.summary.patron.id());
         return false;
     }
 
     compileAlerts(): Promise<any> {
 
         // User navigated to a different patron mid-data load.
-        if (!this.patron) { return Promise.resolve(); }
+        if (!this.summary) { return Promise.resolve(); }
 
-        return this.patrons.compileAlerts(this.patron, this.patronStats)
+        return this.patrons.compileAlerts(this.summary)
         .then(alerts => {
-            this.alerts = alerts;
+            this.summary.alerts = alerts;
 
             if (this.searchBarcode) {
-                const card = this.patron.cards()
+                const card = this.summary.patron.cards()
                     .filter(c => c.barcode() === this.searchBarcode)[0];
-                this.alerts.retrievedWithInactive = card && card.active() === 'f';
+                this.summary.alerts.retrievedWithInactive =
+                    card && card.active() === 'f';
                 this.searchBarcode = null;
             }
         });
index 51b11e8..a569563 100644 (file)
         <div>
           <div class="form-check form-check-inline">
             <input class="form-check-input" type="radio" name="lead" id="lead-record-1"
-              [value]="context1.patron.id()" [(ngModel)]="leadAccount">
+              [value]="summary1.patron.id()" [(ngModel)]="leadAccount">
             <label class="form-check-label" for="lead-record-1">Use as Lead?</label>
           </div>
         </div>
-        <eg-patron-summary [patron]="context1.patron"
-          [stats]="context1.stats" [alerts]="context1.alerts">
-        </eg-patron-summary>  
+        <eg-patron-summary [summary]="summary1"></eg-patron-summary>  
       </div>
       <div class="col-lg-6">
         <div>
           <div class="form-check form-check-inline">
             <input class="form-check-input" type="radio" name="lead" id="lead-record-2" 
-              [value]="context2.patron.id()" [(ngModel)]="leadAccount">
+              [value]="summary2.patron.id()" [(ngModel)]="leadAccount">
             <label class="form-check-label" for="lead-record-2">Use as Lead?</label>
           </div>
         </div>
-        <eg-patron-summary [patron]="context2.patron"
-          [stats]="context2.stats" [alerts]="context2.alerts">
-        </eg-patron-summary>  
+        <eg-patron-summary [summary]="summary2"></eg-patron-summary>  
       </div>
     </div>
   </div>
index c818f37..e5dfee1 100644 (file)
@@ -7,9 +7,10 @@ import {NetService} from '@eg/core/net.service';
 import {EventService} from '@eg/core/event.service';
 import {NgbModal, NgbModalOptions} from '@ng-bootstrap/ng-bootstrap';
 import {DialogComponent} from '@eg/share/dialog/dialog.component';
-import {PatronService, PatronStats, PatronAlerts} from './patron.service';
+import {PatronService, PatronSummary} from './patron.service';
 
 /**
+ * Dialog for merging 2 patron accounts.
  */
 
 const PATRON_FLESH_FIELDS = [
@@ -30,12 +31,6 @@ const PATRON_FLESH_FIELDS = [
     'groups'
 ];
 
-class MergeContext {
-    patron: IdlObject;
-    stats: PatronStats;
-    alerts: PatronAlerts;
-}
-
 @Component({
   selector: 'eg-patron-merge-dialog',
   templateUrl: 'merge-dialog.component.html'
@@ -46,8 +41,8 @@ export class PatronMergeDialogComponent
 
     @Input() patronIds: [number, number];
 
-       context1: MergeContext;
-       context2: MergeContext;
+    summary1: PatronSummary;
+    summary2: PatronSummary;
 
     leadAccount: number = null;
     loading = true;
@@ -65,22 +60,22 @@ export class PatronMergeDialogComponent
             this.loading = true;
             this.leadAccount = null;
             this.loadPatron(this.patronIds[0])
-            .then(ctx => this.context1 = ctx)
+            .then(ctx => this.summary1 = ctx)
             .then(_ => this.loadPatron(this.patronIds[1]))
-            .then(ctx => this.context2 = ctx)
+            .then(ctx => this.summary2 = ctx)
             .then(_ => this.loading = false);
         });
     }
 
-    loadPatron(id: number): Promise<MergeContext> {
-        const ctx = new MergeContext();
+    loadPatron(id: number): Promise<PatronSummary> {
+        const sum = new PatronSummary();
         return this.patrons.getFleshedById(id, PATRON_FLESH_FIELDS)
-        .then(patron => ctx.patron = patron)
-        .then(_ => this.patrons.getVitalStats(ctx.patron))
-        .then(stats => ctx.stats = stats)
-        .then(_ => this.patrons.compileAlerts(ctx.patron, ctx.stats))
-        .then(alerts => ctx.alerts = alerts)
-        .then(_ => ctx);
+        .then(patron => sum.patron = patron)
+        .then(_ => this.patrons.getVitalStats(sum.patron))
+        .then(stats => sum.stats = stats)
+        .then(_ => this.patrons.compileAlerts(sum))
+        .then(alerts => sum.alerts = alerts)
+        .then(_ => sum);
     }
 
     merge() {
index 3333ee0..3e6f635 100644 (file)
@@ -10,23 +10,25 @@ import {Observable} from 'rxjs';
 import {BarcodeSelectComponent} from '@eg/staff/share/barcodes/barcode-select.component';
 import {ServerStoreService} from '@eg/core/server-store.service';
 
-export interface PatronStats {
-    fines: {
-        balance_owed: number,
-        group_balance_owed: number
+export class PatronStats {
+    fines = {
+        balance_owed: 0,
+        group_balance_owed: 0
     };
-    checkouts: {
-        overdue: number,
-        claims_returned: number,
-        lost: number,
-        out: number,
-        total_out: number,
-        long_overdue: number,
-        noncat: number
+
+    checkouts = {
+        overdue: 0,
+        claims_returned: 0,
+        lost: 0,
+        out: 0,
+        total_out: 0,
+        long_overdue: 0,
+        noncat: 0
     };
-    holds: {
-        ready: number;
-        total: number;
+
+    holds = {
+        ready: 0,
+        total: 0
     };
 }
 
@@ -56,7 +58,19 @@ export class PatronAlerts {
     }
 }
 
+export class PatronSummary {
+    id: number;
+    patron: IdlObject;
+    stats: PatronStats = new PatronStats();
+    alerts: PatronAlerts = new PatronAlerts();
 
+    constructor(patron?: IdlObject) {
+        if (patron) {
+            this.id = patron.id();
+            this.patron = patron;
+        }
+    }
+}
 
 @Injectable()
 export class PatronService {
@@ -314,8 +328,10 @@ export class PatronService {
         });
     }
 
-    compileAlerts(patron: IdlObject, stats: PatronStats): Promise<PatronAlerts> {
+    compileAlerts(summary: PatronSummary): Promise<PatronAlerts> {
 
+        const patron = summary.patron;
+        const stats = summary.stats;
         const alerts = new PatronAlerts();
 
         alerts.holdsReady = stats.holds.ready;
index 4112f3b..7a2435c 100644 (file)
@@ -35,10 +35,10 @@ export class PatronPenaltyDialogComponent
     SILENT_NOTE = 21;
     STAFF_CHR = 25;
 
-    staffInitials: string;
+    penalty: IdlObject; // modifying an existing penalty
     penaltyTypes: IdlObject[];
     penaltyTypeFromSelect = '';
-    penaltyTypeFromButton;
+    penaltyTypeFromButton: number;
     patron: IdlObject;
     dataLoaded = false;
     requireInitials = false;
@@ -69,7 +69,21 @@ export class PatronPenaltyDialogComponent
     init(): Observable<any> {
         this.dataLoaded = false;
 
-        this.penaltyTypeFromButton = this.SILENT_NOTE;
+        if (this.penalty) { // Modifying an existing penalty
+            const pen = this.penalty;
+            const sp = pen.standing_penalty().id();
+            if (sp === this.ALERT_NOTE ||
+                sp === this.SILENT_NOTE || sp === this.STAFF_CHR) {
+                this.penaltyTypeFromButton = sp;
+            } else {
+                this.penaltyTypeFromSelect = sp;
+            }
+
+            this.noteText = pen.note();
+
+        } else {
+            this.penaltyTypeFromButton = this.SILENT_NOTE;
+        }
 
         this.org.settings(['ui.staff.require_initials.patron_standing_penalty'])
         .then(sets => this.requireInitials =
@@ -90,8 +104,33 @@ export class PatronPenaltyDialogComponent
         }));
     }
 
+    modifyPenalty() {
+        this.penalty.note(this.initials ?
+            `${this.noteText} [${this.initials}]` : this.noteText);
+
+        this.penalty.standing_penalty(
+            this.penaltyTypeFromSelect || this.penaltyTypeFromButton);
+
+        this.pcrud.update(this.penalty).toPromise()
+        .then(ok => {
+            if (!ok) {
+                this.errorMsg.current().then(msg => this.toast.danger(msg));
+                this.error('Update failed', true);
+            } else {
+                this.successMsg.current().then(msg => this.toast.success(msg));
+                this.penalty = null;
+                this.close(ok);
+            }
+        });
+    }
+
     apply() {
 
+        if (this.penalty) {
+            this.modifyPenalty();
+            return;
+        }
+
         const pen = this.idl.create('ausp');
         const msg = {
             title: this.title,
index c41f46a..931662f 100644 (file)
@@ -4,9 +4,9 @@
   <div class="row d-flex">
     <div class="flex-1 pt-1">
       <h4 class="font-weight-bold" i18n>
-        {{patron.family_name()}}, 
-        {{patron.first_given_name()}} 
-        {{patron.second_given_name()}}
+        {{p().family_name()}}, 
+        {{p().first_given_name()}} 
+        {{p().second_given_name()}}
       </h4>
     </div>
     <ng-container *ngIf="hasPrefName()">
   <div class="row d-flex border-top" *ngIf="hasPrefName()">
     <div class="flex-1 pt-1">
       <h4 class="font-weight-bold" i18n>
-        {{patronService.namePart(patron, 'family_name')}}, 
-        {{patronService.namePart(patron, 'first_given_name')}} 
-        {{patronService.namePart(patron, 'second_given_name')}}
+        {{patronService.namePart(p(), 'family_name')}}, 
+        {{patronService.namePart(p(), 'first_given_name')}} 
+        {{patronService.namePart(p(), 'second_given_name')}}
       </h4>
     </div>
     <div class="mr-2 ml-2 text-info font-italic" i18n>preferred</div>
   </div>
 
   <div class="row mb-1 alert alert-danger p-0" 
-    *ngIf="alerts.accountExpiresSoon">
+    *ngIf="summary.alerts.accountExpiresSoon">
     <div class="col-lg-12" i18n>
       Patron account will expire soon.  Please renew.
     </div>
   </div>
 
   <div class="row mb-1 alert alert-danger p-0" 
-    *ngFor="let pen of alerts.alertPenalties">
+    *ngFor="let pen of summary.alerts.alertPenalties">
     <div class="col-lg-9"
       title="{{pen.standing_penalty().name()}}">
       {{pen.note() || pen.standing_penalty().label()}}
 
   <div class="row mb-1">
     <div class="col-lg-5" i18n>Profile</div>
-    <div class="col-lg-7">{{patron.profile().name()}}</div>
+    <div class="col-lg-7">{{p().profile().name()}}</div>
   </div>
   <div class="row mb-1">
     <div class="col-lg-5" i18n>Home Library</div>
-    <div class="col-lg-7">{{orgSn(patron.home_ou())}}</div>
+    <div class="col-lg-7">{{orgSn(p().home_ou())}}</div>
   </div>
   <div class="row mb-1">
     <div class="col-lg-5" i18n>Net Access</div>
-    <div class="col-lg-7">{{patron.net_access_level().name()}}</div>
+    <div class="col-lg-7">{{p().net_access_level().name()}}</div>
   </div>
   <div class="row mb-1">
     <div class="col-lg-5" i18n>Date of Birth</div>
-    <div class="col-lg-7">{{patron.dob() | date:'shortDate'}}</div>
+    <div class="col-lg-7">{{p().dob() | date:'shortDate'}}</div>
   </div>
   <div class="row mb-1">
     <div class="col-lg-5" i18n>Parent/Guardian</div>
-    <div class="col-lg-7">{{patron.guardian()}}</div>
+    <div class="col-lg-7">{{p().guardian()}}</div>
   </div>
   <div class="row mb-1">
     <div class="col-lg-5" i18n>Last Activity</div>
     <div class="col-lg-7">
-      <ng-container *ngIf="patron.usr_activity()[0]">
-        {{patron.usr_activity()[0].event_time() | date:'shortDate'}}
+      <ng-container *ngIf="p().usr_activity()[0]">
+        {{p().usr_activity()[0].event_time() | date:'shortDate'}}
       </ng-container>
     </div>
   </div>
   <div class="row mb-1">
     <div class="col-lg-5" i18n>Last Updated</div>
-    <div class="col-lg-7">{{patron.last_update_time() | date:'shortDate'}}</div>
+    <div class="col-lg-7">{{p().last_update_time() | date:'shortDate'}}</div>
   </div>
   <div class="row mb-1">
     <div class="col-lg-5" i18n>Create Date</div>
-    <div class="col-lg-7">{{patron.create_date() | date:'shortDate'}}</div>
+    <div class="col-lg-7">{{p().create_date() | date:'shortDate'}}</div>
   </div>
-  <div class="row" [ngClass]="{'alert alert-danger p-0': alerts.accountExpired}">
+  <div class="row" [ngClass]="{'alert alert-danger p-0': summary.alerts.accountExpired}">
     <div class="col-lg-5" i18n>Expire Date</div>
-    <div class="col-lg-7">{{patron.expire_date() | date:'shortDate'}}</div>
+    <div class="col-lg-7">{{p().expire_date() | date:'shortDate'}}</div>
   </div>
 
   <hr class="m-1"/>
 
-  <ng-container *ngIf="stats">
+  <ng-container *ngIf="summary">
 
     <div class="row mb-1"
-      [ngClass]="{'alert alert-danger p-0': stats.fines.balance_owed > 0}">
+      [ngClass]="{'alert alert-danger p-0': summary.stats.fines.balance_owed > 0}">
       <div class="col-lg-5" i18n>Fines Owed</div>
-      <div class="col-lg-7">{{stats.fines.balance_owed | currency}}</div>
+      <div class="col-lg-7">{{summary.stats.fines.balance_owed | currency}}</div>
     </div>
 
     <ng-container 
-      *ngIf="stats.fines.group_balance_owed > stats.fines.balance_owed">
+      *ngIf="summary.stats.fines.group_balance_owed > summary.stats.fines.balance_owed">
       <div class="row mb-1 alert alert-danger p-0">
         <div class="col-lg-5" i18n>Group Fines</div>
-        <div class="col-lg-7">{{stats.fines.group_balance_owed | currency}}</div>
+        <div class="col-lg-7">{{summary.stats.fines.group_balance_owed | currency}}</div>
       </div>
     </ng-container>
 
     <div class="row mb-1">
       <div class="col-lg-5" i18n>Items Out</div>
-      <div class="col-lg-7">{{stats.checkouts.total_out}}</div>
+      <div class="col-lg-7">{{summary.stats.checkouts.total_out}}</div>
     </div>
     <div class="row mb-1"
-      [ngClass]="{'alert alert-danger p-0': stats.checkouts.overdue > 0}">
+      [ngClass]="{'alert alert-danger p-0': summary.stats.checkouts.overdue > 0}">
       <div class="col-lg-5" i18n>Overdue</div>
-      <div class="col-lg-7">{{stats.checkouts.overdue}}</div>
+      <div class="col-lg-7">{{summary.stats.checkouts.overdue}}</div>
     </div>
     <div class="row mb-1"
-      [ngClass]="{'alert alert-danger p-0': stats.checkouts.long_overdue > 0}">
+      [ngClass]="{'alert alert-danger p-0': summary.stats.checkouts.long_overdue > 0}">
       <div class="col-lg-5" i18n>Long Overdue</div>
-      <div class="col-lg-7">{{stats.checkouts.long_overdue}}</div>
+      <div class="col-lg-7">{{summary.stats.checkouts.long_overdue}}</div>
     </div>
     <div class="row mb-1"
-      [ngClass]="{'alert alert-danger p-0': stats.checkouts.claims_returned > 0}">
+      [ngClass]="{'alert alert-danger p-0': summary.stats.checkouts.claims_returned > 0}">
       <div class="col-lg-5" i18n>Claimed Returned</div>
-      <div class="col-lg-7">{{stats.checkouts.claims_returned}}</div>
+      <div class="col-lg-7">{{summary.stats.checkouts.claims_returned}}</div>
     </div>
     <div class="row mb-1"
-      [ngClass]="{'alert alert-danger p-0': stats.checkouts.lost > 0}">
+      [ngClass]="{'alert alert-danger p-0': summary.stats.checkouts.lost > 0}">
       <div class="col-lg-5" i18n>Lost</div>
-      <div class="col-lg-7">{{stats.checkouts.lost}}</div>
+      <div class="col-lg-7">{{summary.stats.checkouts.lost}}</div>
     </div>
     <div class="row mb-1">
       <div class="col-lg-5" i18n>Non-Cataloged</div>
-      <div class="col-lg-7">{{stats.checkouts.noncat}}</div>
+      <div class="col-lg-7">{{summary.stats.checkouts.noncat}}</div>
     </div>
     <div class="row">
       <div class="col-lg-5" i18n>Holds</div>
       <div class="col-lg-7">
-        {{stats.holds.ready}} / {{stats.holds.total}}
+        {{summary.stats.holds.ready}} / {{summary.stats.holds.total}}
       </div>
     </div>
 
   <div class="row mb-1">
     <div class="col-lg-5" i18n>Card</div>
     <div class="col-lg-7">
-      {{patron.card() ? patron.card().barcode() : ''}}
+      {{p().card() ? p().card().barcode() : ''}}
     </div>
   </div>
   <div class="row mb-1">
     <div class="col-lg-5" i18n>Username</div>
-    <div class="col-lg-7">{{patron.usrname()}}</div>
+    <div class="col-lg-7">{{p().usrname()}}</div>
   </div>
   <div class="row mb-1">
     <div class="col-lg-5" i18n>Day Phone</div>
-    <div class="col-lg-7">{{patron.day_phone()}}</div> 
+    <div class="col-lg-7">{{p().day_phone()}}</div> 
   </div>
   <div class="row mb-1">
     <div class="col-lg-5" i18n>Evening Phone</div>
-    <div class="col-lg-7">{{patron.evening_phone()}}</div> 
+    <div class="col-lg-7">{{p().evening_phone()}}</div> 
   </div>
   <div class="row mb-1">
     <div class="col-lg-5" i18n>Other Phone</div>
-    <div class="col-lg-7">{{patron.other_phone()}}</div> 
+    <div class="col-lg-7">{{p().other_phone()}}</div> 
   </div>
   <div class="row mb-1">
     <div class="col-lg-5" i18n>ID1 </div>
-    <div class="col-lg-7">{{patron.ident_value()}}</div> 
+    <div class="col-lg-7">{{p().ident_value()}}</div> 
   </div>
   <div class="row mb-1">
     <div class="col-lg-5" i18n>ID2</div>
-    <div class="col-lg-7">{{patron.ident_value2()}}</div> 
+    <div class="col-lg-7">{{p().ident_value2()}}</div> 
   </div>
   <div class="row mb-1">
     <div class="col-lg-5" i18n>Email</div>
     <div class="col-lg-7">
       <!-- TODO: mailto link -->
-      {{patron.email()}}
+      {{p().email()}}
     </div> 
   </div>
 
   <hr class="m-1"/>
 
-  <div class="row mb-1" *ngFor="let addr of patron.addresses()">
+  <div class="row mb-1" *ngFor="let addr of p().addresses()">
     <div class="col-lg-12">
       <fieldset>
         <legend class="d-flex" [ngClass]="{'alert alert-danger p-0': addr.valid() == 'f'}">
       <!-- hidden textare used only for copying the text -->
       <textarea id="patron-address-copy-{{addr.id()}}" rows="2"              
         style="visibility:hidden">
-{{patron.first_given_name()}} {{patron.second_given_name()}} {{patron.family_name()}}
+{{p().first_given_name()}} {{p().second_given_name()}} {{p().family_name()}}
 {{addr.street1()}} {{addr.street2()}}
 {{addr.city()}}, {{addr.state()}} {{addr.post_code()}}</textarea>
     </div>
index a80835e..11dfe00 100644 (file)
@@ -5,7 +5,7 @@ import {OrgService} from '@eg/core/org.service';
 import {IdlObject} from '@eg/core/idl.service';
 import {NetService} from '@eg/core/net.service';
 import {PrintService} from '@eg/share/print/print.service';
-import {PatronService, PatronStats, PatronAlerts} from './patron.service';
+import {PatronService, PatronSummary} from './patron.service';
 
 @Component({
   templateUrl: 'summary.component.html',
@@ -14,9 +14,7 @@ import {PatronService, PatronStats, PatronAlerts} from './patron.service';
 })
 export class PatronSummaryComponent implements OnInit {
 
-    @Input() patron: IdlObject;
-    @Input() stats: PatronStats;
-    @Input() alerts: PatronAlerts;
+    @Input() summary: PatronSummary;
 
     constructor(
         private org: OrgService,
@@ -28,12 +26,16 @@ export class PatronSummaryComponent implements OnInit {
     ngOnInit() {
     }
 
+    p(): IdlObject { // patron shorthand
+        return this.summary ? this.summary.patron : null;
+    }
+
     hasPrefName(): boolean {
-        if (this.patron) {
+        if (this.p()) {
             return (
-                this.patron.pref_first_given_name() ||
-                this.patron.pref_second_given_name() ||
-                this.patron.pref_family_name()
+                this.p().pref_first_given_name() ||
+                this.p().pref_second_given_name() ||
+                this.p().pref_family_name()
             );
         }
     }
@@ -42,7 +44,7 @@ export class PatronSummaryComponent implements OnInit {
         this.printer.print({
             templateName: 'patron_address',
             contextData: {
-                patron: this.patron,
+                patron: this.p(),
                 address: addr
             },
             printContext: 'default'