3c677cd26610d49b0391e9ca4d7923208e611799
[evergreen-equinox.git] / Open-ILS / src / eg2 / src / app / staff / circ / patron / patron.service.ts
1 import {Injectable, EventEmitter} from '@angular/core';
2 import {IdlObject} from '@eg/core/idl.service';
3 import {NetService} from '@eg/core/net.service';
4 import {OrgService} from '@eg/core/org.service';
5 import {AuthService} from '@eg/core/auth.service';
6 import {PatronService, PatronSummary, PatronStats, PatronAlerts
7     } from '@eg/staff/share/patron/patron.service';
8 import {PatronSearch} from '@eg/staff/share/patron/search.component';
9 import {StoreService} from '@eg/core/store.service';
10 import {ServerStoreService} from '@eg/core/server-store.service';
11 import {CircService, CircDisplayInfo} from '@eg/staff/share/circ/circ.service';
12
13 export interface BillGridEntry extends CircDisplayInfo {
14     xact: IdlObject; // mbt
15     billingLocation?: string;
16     paymentPending?: number;
17 }
18
19 export interface CircGridEntry {
20     index: number;
21     title?: string;
22     author?: string;
23     isbn?: string;
24     copy?: IdlObject;
25     circ?: IdlObject;
26     volume?: IdlObject;
27     record?: IdlObject;
28     dueDate?: string;
29     copyAlertCount: number;
30     nonCatCount: number;
31     patron: IdlObject;
32 }
33
34 const PATRON_FLESH_FIELDS = [
35     'card',
36     'cards',
37     'settings',
38     'standing_penalties',
39     'addresses',
40     'billing_address',
41     'mailing_address',
42     'waiver_entries',
43     'usr_activity',
44     'notes',
45     'profile',
46     'net_access_level',
47     'ident_type',
48     'ident_type2',
49     'groups'
50 ];
51
52 @Injectable()
53 export class PatronContextService {
54
55     summary: PatronSummary;
56     loaded = false;
57     lastPatronSearch: PatronSearch;
58     searchBarcode: string = null;
59
60     // These should persist tab changes
61     checkouts: CircGridEntry[] = [];
62
63     maxRecentPatrons = 1;
64
65     settingsCache: {[key: string]: any} = {};
66
67     constructor(
68         private store: StoreService,
69         private serverStore: ServerStoreService,
70         private org: OrgService,
71         private circ: CircService,
72         public patrons: PatronService
73     ) {}
74
75     loadPatron(id: number): Promise<any> {
76         this.loaded = false;
77         this.checkouts = [];
78         return this.refreshPatron(id).then(_ => this.loaded = true);
79     }
80
81     // Update the patron data without resetting all of the context data.
82     refreshPatron(id?: number): Promise<any> {
83
84         if (!id) {
85             if (!this.summary) {
86                 return Promise.reject('no can do');
87             } else {
88                 id = this.summary.id;
89             }
90         }
91
92         return this.patrons.getFleshedById(id, PATRON_FLESH_FIELDS)
93         .then(p => this.summary = new PatronSummary(p))
94         .then(_ => this.getPatronStats(id))
95         .then(_ => this.compileAlerts())
96         .then(_ => this.addRecentPatron());
97     }
98
99     addRecentPatron(patronId?: number): Promise<any> {
100
101         if (!patronId) { patronId = this.summary.id; }
102
103         return this.serverStore.getItem('ui.staff.max_recent_patrons')
104         .then(num => {
105             if (num) { this.maxRecentPatrons = num; }
106
107             const patrons: number[] =
108                 this.store.getLoginSessionItem('eg.circ.recent_patrons') || [];
109
110             patrons.splice(0, 0, patronId);  // put this user at front
111             patrons.splice(this.maxRecentPatrons, 1); // remove excess
112
113             // remove any other occurrences of this user, which may have been
114             // added before the most recent user.
115             const idx = patrons.indexOf(patronId, 1);
116             if (idx > 0) { patrons.splice(idx, 1); }
117
118             this.store.setLoginSessionItem('eg.circ.recent_patrons', patrons);
119         });
120     }
121
122     getPatronStats(id: number): Promise<any> {
123
124         // When quickly navigating patron search results it's possible
125         // for this.patron to be cleared right before this function
126         // is called.  Exit early instead of making an unneeded call.
127         if (!this.summary) { return Promise.resolve(); }
128
129         return this.patrons.getVitalStats(this.summary.patron)
130         .then(stats => this.summary.stats = stats);
131     }
132
133     patronAlertsShown(): boolean {
134         if (!this.summary) { return false; }
135         const shown = this.store.getSessionItem('eg.circ.last_alerted_patron');
136         if (shown === this.summary.patron.id()) { return true; }
137         this.store.setSessionItem('eg.circ.last_alerted_patron', this.summary.patron.id());
138         return false;
139     }
140
141     compileAlerts(): Promise<any> {
142
143         // User navigated to a different patron mid-data load.
144         if (!this.summary) { return Promise.resolve(); }
145
146         return this.patrons.compileAlerts(this.summary)
147         .then(alerts => {
148             this.summary.alerts = alerts;
149
150             if (this.searchBarcode) {
151                 const card = this.summary.patron.cards()
152                     .filter(c => c.barcode() === this.searchBarcode)[0];
153                 this.summary.alerts.retrievedWithInactive =
154                     card && card.active() === 'f';
155                 this.searchBarcode = null;
156             }
157         });
158     }
159
160     orgSn(orgId: number): string {
161         const org = this.org.get(orgId);
162         return org ? org.shortname() : '';
163     }
164
165     formatXactForDisplay(xact: IdlObject): BillGridEntry {
166
167         const entry: BillGridEntry = {
168             xact: xact,
169             paymentPending: 0
170         };
171
172         if (xact.summary().xact_type() !== 'circulation') {
173
174             entry.xact.grocery().billing_location(
175                 this.org.get(entry.xact.grocery().billing_location()));
176
177             entry.title = xact.summary().last_billing_type();
178             entry.billingLocation =
179                 xact.grocery().billing_location().shortname();
180             return entry;
181         }
182
183         entry.xact.circulation().circ_lib(
184             this.org.get(entry.xact.circulation().circ_lib()));
185
186         const circDisplay: CircDisplayInfo =
187             this.circ.getDisplayInfo(xact.circulation());
188
189         entry.billingLocation =
190             xact.circulation().circ_lib().shortname();
191
192         return Object.assign(entry, circDisplay);
193     }
194 }
195
196