--- /dev/null
+
+<div *ngIf="context.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>
+
+ <div class="mt-2 alert alert-warning" *ngIf="context.alerts.accountExpired" i18n>
+ Patron account is EXPIRED.
+ </div>
+
+ <div class="mt-2 alert alert-warning" *ngIf="context.alerts.accountExpiresSoon" i18n>
+ Patron account will expire soon. Please renew.
+ </div>
+
+ <div class="mt-2 alert alert-danger" *ngIf="context.alerts.patronBarred" i18n>
+ Patron account is BARRED
+ </div>
+
+ <div class="mt-2 alert alert-warning" *ngIf="context.alerts.patronInactive" i18n>
+ Patron account is INACTIVE
+ </div>
+
+ <div class="mt-2 alert alert-warning" *ngIf="context.alerts.retrievedWithInactive" i18n>
+ Patron account retrieved with an INACTIVE card.
+ </div>
+
+ <div class="mt-2 alert alert-warning" *ngIf="context.alerts.invalidAddress" i18n>
+ Patron account has invalid addresses.
+ </div>
+
+ <!-- alert message -->
+ <div class="row" *ngIf="context.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>
+ </div>
+ </div>
+
+ <!-- penalties -->
+ <div class="row" *ngIf="context.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">
+ <div class="row">
+ <div class="col-lg-2">
+ {{context.orgSn(penalty.org_unit())}}
+ </div>
+ <div class="col-lg-8"
+ title="{{penalty.standing_penalty().name()}}">
+ {{penalty.standing_penalty().label()}}
+ <div>{{penalty.note()}}</div><!-- force newline -->
+ </div>
+ <div class="col-lg-2">
+ {{penalty.set_date() | date:'short'}}
+ </div>
+ </div>
+ </li>
+ </ul>
+ </div>
+ </div>
+ </div>
+ </div>
+ <div class="mt-4 well-value" i18n>
+ Select a tab above (for example, Check Out) to clear this alert.
+ </div>
+</div>
--- /dev/null
+import {Component, OnInit, Input} from '@angular/core';
+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 {PatronContextService} from './patron.service';
+
+@Component({
+ templateUrl: 'alerts.component.html',
+ selector: 'eg-patron-alerts'
+})
+export class PatronAlertsComponent implements OnInit {
+
+ constructor(
+ private org: OrgService,
+ private net: NetService,
+ public patronService: PatronService,
+ public context: PatronContextService
+ ) {}
+
+ ngOnInit() {
+ }
+}
+
import {OrgService} from '@eg/core/org.service';
import {NetService} from '@eg/core/net.service';
import {PatronService} from '@eg/staff/share/patron/patron.service';
-import {PatronManagerService, CircGridEntry} from './patron.service';
+import {PatronContextService, CircGridEntry} from './patron.service';
import {CheckoutParams, CheckoutResult, CircService
} from '@eg/staff/share/circ/circ.service';
import {PromptDialogComponent} from '@eg/share/dialog/prompt.component';
private net: NetService,
public circ: CircService,
public patronService: PatronService,
- public context: PatronManagerService,
+ public context: PatronContextService,
private audio: AudioService
) {}
import {OrgService} from '@eg/core/org.service';
import {NetService} from '@eg/core/net.service';
import {PatronService} from '@eg/staff/share/patron/patron.service';
-import {PatronManagerService} from './patron.service';
+import {PatronContextService} from './patron.service';
@Component({
templateUrl: 'edit-toolbar.component.html',
private org: OrgService,
private net: NetService,
public patronService: PatronService,
- public context: PatronManagerService
+ public context: PatronContextService
) {}
ngOnInit() {
import {OrgService} from '@eg/core/org.service';
import {NetService} from '@eg/core/net.service';
import {PatronService} from '@eg/staff/share/patron/patron.service';
-import {PatronManagerService} from './patron.service';
+import {PatronContextService} from './patron.service';
@Component({
templateUrl: 'edit.component.html',
private org: OrgService,
private net: NetService,
public patronService: PatronService,
- public context: PatronManagerService
+ public context: PatronContextService
) {}
ngOnInit() {
import {OrgService} from '@eg/core/org.service';
import {NetService} from '@eg/core/net.service';
import {PatronService} from '@eg/staff/share/patron/patron.service';
-import {PatronManagerService} from './patron.service';
+import {PatronContextService} from './patron.service';
@Component({
templateUrl: 'holds.component.html',
private org: OrgService,
private net: NetService,
public patronService: PatronService,
- public context: PatronManagerService
+ public context: PatronContextService
) {}
ngOnInit() {
import {PcrudService} from '@eg/core/pcrud.service';
import {AuthService} from '@eg/core/auth.service';
import {PatronService} from '@eg/staff/share/patron/patron.service';
-import {PatronManagerService} from './patron.service';
+import {PatronContextService} from './patron.service';
import {CheckoutResult, CircService} from '@eg/staff/share/circ/circ.service';
import {PromptDialogComponent} from '@eg/share/dialog/prompt.component';
import {GridDataSource, GridColumn, GridCellTextGenerator} from '@eg/share/grid/grid';
private store: StoreService,
private serverStore: ServerStoreService,
public patronService: PatronService,
- public context: PatronManagerService
+ public context: PatronContextService
) {}
ngOnInit() {
</ng-container>
<li ngbNavItem="checkout" [disabled]="!context.patron">
- <a ngbNavLink i18n>Checkout</a>
+ <a ngbNavLink i18n>Check Out</a>
<ng-template ngbNavContent>
<div class="">
<eg-patron-checkout></eg-patron-checkout>
(click)="false" class="nav-link" ngbDropdownToggle>Other</a>
<div ngbDropdownMenu>
<a routerLink="/staff/circ/patron/{{patronId}}/alerts"
- ngbDropdownItem i18n>Alerts</a>
+ ngbDropdownItem i18n>Alerts and Messages</a>
<a routerLink="/staff/circ/patron/{{patronId}}/notes"
ngbDropdownItem i18n>Notes</a>
<a routerLink="/staff/circ/patron/{{patronId}}/triggered_events"
ngbDropdownItem i18n>Completely Purge Account</a>
</div>
<ng-template ngbNavContent>
- <!-- display selected altTab component -->
- OTHER STUFF: {{altTab}}
+ <ng-container [ngSwitch]="altTab">
+ <div *ngSwitchCase="'alerts'">
+ <eg-patron-alerts></eg-patron-alerts>
+ </div>
+ </ng-container>
</ng-template>
</li>
import {AuthService} from '@eg/core/auth.service';
import {ServerStoreService} from '@eg/core/server-store.service';
import {PatronService} from '@eg/staff/share/patron/patron.service';
-import {PatronManagerService} from './patron.service';
+import {PatronContextService} from './patron.service';
import {PatronSearch, PatronSearchComponent
} from '@eg/staff/share/patron/search.component';
private auth: AuthService,
private store: ServerStoreService,
public patronService: PatronService,
- public context: PatronManagerService
+ public context: PatronContextService
) {}
ngOnInit() {
if (this.patronId) {
if (this.patronId !== prevId) { // different patron
- this.context.loadPatron(this.patronId);
+ this.changePatron(this.patronId);
}
} else {
// Use the ID of the previously loaded patron.
const id = ids[0];
if (id !== this.patronId) {
- this.patronId = id;
- this.context.loadPatron(id);
- return;
+ this.changePatron(id);
}
}
+ changePatron(id: number) {
+ this.patronId = id;
+ this.context.loadPatron(id)
+ .then(_ => {
+ if (this.context.patron &&
+ this.context.alerts.hasAlerts() &&
+ !this.context.patronAlertsShown()) {
+ this.router.navigate(['/staff/circ/patron', id, 'alerts'])
+ }
+ });
+ }
+
// Route to checkout tab for selected patron.
patronsActivated(rows: any[]) {
if (rows.length !== 1) { return; }
import {NgModule} from '@angular/core';
import {PatronRoutingModule} from './routing.module';
+import {PatronResolver} from './resolver.service';
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 {PatronManagerService} from './patron.service';
+import {PatronContextService} from './patron.service';
import {PatronComponent} from './patron.component';
+import {PatronAlertsComponent} from './alerts.component';
import {SummaryComponent} from './summary.component';
import {CheckoutComponent} from './checkout.component';
import {HoldsComponent} from './holds.component';
@NgModule({
declarations: [
PatronComponent,
+ PatronAlertsComponent,
SummaryComponent,
CheckoutComponent,
HoldsComponent,
BarcodesModule
],
providers: [
- PatronManagerService
+ PatronResolver,
+ PatronContextService
]
})
import {Injectable} from '@angular/core';
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} from '@eg/staff/share/patron/patron.service';
import {PatronSearch} from '@eg/staff/share/patron/search.component';
+import {StoreService} from '@eg/core/store.service';
export interface CircGridEntry {
title?: string;
};
}
+export class PatronAlerts {
+ holdsReady = 0;
+ accountExpired = false;
+ accountExpiresSoon = false;
+ patronBarred = false;
+ patronInactive = false;
+ retrievedWithInactive = false;
+ invalidAddress = false;
+ alertMessage: string = null;
+ alertPenalties: IdlObject[] = [];
+
+ hasAlerts(): boolean {
+ return (
+ this.holdsReady > 0 ||
+ this.accountExpired ||
+ this.accountExpiresSoon ||
+ this.patronBarred ||
+ this.patronInactive ||
+ this.retrievedWithInactive ||
+ this.invalidAddress ||
+ this.alertMessage !== null ||
+ this.alertPenalties.length > 0
+ );
+ }
+}
+
@Injectable()
-export class PatronManagerService {
+export class PatronContextService {
patron: IdlObject;
patronStats: PatronStats;
+ alerts: PatronAlerts;
- // Value for YAOUS circ.do_not_tally_claims_returned
- noTallyClaimsReturned = false;
-
- // Value for YAOUS circ.tally_lost
- tallyLost = false;
+ noTallyClaimsReturned = false; // circ.do_not_tally_claims_returned
+ tallyLost = false; // circ.tally_lost
loaded = false;
- accountExpired = false;
- accountExpiresSoon = false;
-
lastPatronSearch: PatronSearch;
+ searchBarcode: string = null;
// These should persist tab changes
checkouts: CircGridEntry[] = [];
constructor(
+ private store: StoreService,
private net: NetService,
+ private org: OrgService,
private auth: AuthService,
public patronService: PatronService
) {}
this.loaded = false;
this.patron = null;
this.checkouts = [];
+ this.alerts = new PatronAlerts();
return this.net.request(
'open-ils.actor',
this.auth.token(), id, PATRON_FLESH_FIELDS).toPromise()
.then(p => this.patron = p)
.then(_ => this.getPatronStats(id))
- .then(_ => this.setExpires())
+ .then(_ => this.compileAlerts())
.then(_ => this.loaded = true);
}
- setExpires(): Promise<any> {
- this.accountExpired = false;
- this.accountExpiresSoon = false;
-
- // 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.
- // For this func. in particular a nasty JS error is thrown.
- if (!this.patron) { return Promise.resolve(); }
-
- return this.patronService.testExpire(this.patron)
- .then(value => {
- if (value === 'expired') {
- this.accountExpired = true;
- } else if (value === 'soon') {
- this.accountExpiresSoon = true;
- }
- });
- }
-
getPatronStats(id: number): Promise<any> {
// When quickly navigating patron search results it's possible
}
});
}
+
+ patronAlertsShown(): boolean {
+ if (!this.patron) { 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());
+ return false;
+ }
+
+ compileAlerts(): Promise<any> {
+
+ // User navigated to a different patron mid-data load.
+ if (!this.patron) { return Promise.resolve(); }
+
+ this.alerts.holdsReady = this.patronStats.holds.ready;
+ this.alerts.patronBarred = this.patron.barred() === 't';
+ this.alerts.patronInactive = this.patron.active() === 'f';
+ this.alerts.invalidAddress = this.patron.addresses()
+ .filter(a => a.valid() === 'f').length > 0;
+ this.alerts.alertMessage = this.patron.alert_message();
+ this.alerts.alertPenalties = this.patron.standing_penalties()
+ .filter(p => p.standing_penalty().staff_alert() === 't');
+
+ if (this.searchBarcode) {
+ const card = this.patron.cards()
+ .filter(c => c.barcode() === this.searchBarcode)[0];
+ this.alerts.retrievedWithInactive = card && card.active() === 'f';
+ this.searchBarcode = null;
+ }
+
+ return this.patronService.testExpire(this.patron)
+ .then(value => {
+ if (value === 'expired') {
+ this.alerts.accountExpired = true;
+ } else if (value === 'soon') {
+ this.alerts.accountExpiresSoon = true;
+ }
+ });
+ }
+
+ orgSn(orgId: number): string {
+ const org = this.org.get(orgId);
+ return org ? org.shortname() : '';
+ }
}
--- /dev/null
+import {Injectable} from '@angular/core';
+import {Router, Resolve, RouterStateSnapshot,
+ ActivatedRouteSnapshot} from '@angular/router';
+import {ServerStoreService} from '@eg/core/server-store.service';
+import {NetService} from '@eg/core/net.service';
+import {OrgService} from '@eg/core/org.service';
+import {AuthService} from '@eg/core/auth.service';
+import {PatronContextService} from './patron.service';
+
+
+@Injectable()
+export class PatronResolver implements Resolve<Promise<any[]>> {
+
+ constructor(
+ private store: ServerStoreService,
+ private context: PatronContextService
+ ) {}
+
+ resolve(
+ route: ActivatedRouteSnapshot,
+ state: RouterStateSnapshot): Promise<any[]> {
+
+ return this.fetchSettings();
+ }
+
+ fetchSettings(): Promise<any> {
+
+ return this.store.getItemBatch([
+ 'eg.circ.patron.summary.collapse',
+ 'circ.do_not_tally_claims_returned',
+ 'circ.tally_lost'
+
+ ]).then(settings => {
+ this.context.noTallyClaimsReturned =
+ settings['circ.do_not_tally_claims_returned'];
+ this.context.tallyLost = settings['circ.tally_lost'];
+ });
+ }
+}
+
import {RouterModule, Routes} from '@angular/router';
import {PatronComponent} from './patron.component';
import {BcSearchComponent} from './bcsearch.component';
+import {PatronResolver} from './resolver.service';
const routes: Routes = [{
path: '',
import('./event-log/event-log.module').then(m => m.EventLogModule)
}, {
path: 'search',
- component: PatronComponent
+ component: PatronComponent,
+ resolve: {resolver : PatronResolver}
}, {
path: 'bcsearch',
component: BcSearchComponent
path: 'bcsearch/:barcode',
component: BcSearchComponent
}, {
+ path: ':id',
+ redirectTo: ':id/checkout'
+ }, {
path: ':id/:tab',
component: PatronComponent,
+ resolve: {resolver : PatronResolver}
}];
@NgModule({
</div>
<div class="row mb-1">
<div class="col-lg-5" i18n>Home Library</div>
- <div class="col-lg-7">{{orgSn(context.patron.home_ou())}}</div>
+ <div class="col-lg-7">{{context.orgSn(context.patron.home_ou())}}</div>
</div>
<div class="row mb-1">
<div class="col-lg-5" i18n>Net Access</div>
import {OrgService} from '@eg/core/org.service';
import {NetService} from '@eg/core/net.service';
import {PatronService} from '@eg/staff/share/patron/patron.service';
-import {PatronManagerService} from './patron.service';
+import {PatronContextService} from './patron.service';
@Component({
templateUrl: 'summary.component.html',
private org: OrgService,
private net: NetService,
public patronService: PatronService,
- public context: PatronManagerService
+ public context: PatronContextService
) {}
ngOnInit() {
}
-
- orgSn(orgId: number): string {
- const org = this.org.get(orgId);
- return org ? org.shortname() : '';
- }
}