LP1851882 Angular catalog recall/force/part holds
[Evergreen.git] / Open-ILS / src / eg2 / src / app / staff / share / holds / holds.service.ts
1 /**
2  * Common code for mananging holds
3  */
4 import {Injectable} from '@angular/core';
5 import {Observable} from 'rxjs';
6 import {map, mergeMap} from 'rxjs/operators';
7 import {IdlObject} from '@eg/core/idl.service';
8 import {NetService} from '@eg/core/net.service';
9 import {PcrudService} from '@eg/core/pcrud.service';
10 import {EventService, EgEvent} from '@eg/core/event.service';
11 import {AuthService} from '@eg/core/auth.service';
12 import {BibRecordService,
13     BibRecordSummary} from '@eg/share/catalog/bib-record.service';
14
15 // Response from a place-holds API call.
16 export interface HoldRequestResult {
17     success: boolean;
18     holdId?: number;
19     evt?: EgEvent;
20 }
21
22 // Values passed to the place-holds API call.
23 export interface HoldRequest {
24     holdType: string;
25     holdTarget: number;
26     recipient: number;
27     requestor: number;
28     pickupLib: number;
29     override?: boolean;
30     notifyEmail?: boolean;
31     notifyPhone?: string;
32     notifySms?: string;
33     smsCarrier?: string;
34     thawDate?: string; // ISO date
35     frozen?: boolean;
36     holdableFormats?: {[target: number]: string};
37     result?: HoldRequestResult;
38 }
39
40 // A fleshed hold request target object containing whatever data is
41 // available for each hold type / target.  E.g. a TITLE hold will
42 // not have a value for 'callNum', but a COPY hold will, since all
43 // copies have call numbers.  Every HoldRequestTarget will have a bibId and
44 // bibSummary.  Some values come directly from the API call, others
45 // applied locally.
46 export interface HoldRequestTarget {
47     target: number;
48     metarecord?: IdlObject;
49     bibrecord?: IdlObject;
50     bibId?: number;
51     bibSummary?: BibRecordSummary;
52     part?: IdlObject;
53     parts?: IdlObject[];
54     callNum?: IdlObject;
55     copy?: IdlObject;
56     issuance?: IdlObject;
57     metarecord_filters?: any;
58 }
59
60 /** Service for performing various hold-related actions */
61
62 @Injectable()
63 export class HoldsService {
64
65     constructor(
66         private evt: EventService,
67         private net: NetService,
68         private auth: AuthService,
69         private bib: BibRecordService,
70     ) {}
71
72     placeHold(request: HoldRequest): Observable<HoldRequest> {
73
74         let method = 'open-ils.circ.holds.test_and_create.batch';
75         if (request.override) { method = method + '.override'; }
76
77         return this.net.request(
78             'open-ils.circ', method, this.auth.token(), {
79                 patronid:       request.recipient,
80                 pickup_lib:     request.pickupLib,
81                 hold_type:      request.holdType,
82                 email_notify:   request.notifyEmail,
83                 phone_notify:   request.notifyPhone,
84                 thaw_date:      request.thawDate,
85                 frozen:         request.frozen,
86                 sms_notify:     request.notifySms,
87                 sms_carrier:    request.smsCarrier,
88                 holdable_formats_map: request.holdableFormats
89             },
90             [request.holdTarget]
91         ).pipe(map(
92             resp => {
93                 let result = resp.result;
94                 const holdResult: HoldRequestResult = {success: true};
95
96                 // API can return an ID, an array of events, or a hash
97                 // of info.
98
99                 if (Number(result) > 0) {
100                     // On success, the API returns the hold ID.
101                     holdResult.holdId = result;
102                     console.debug(`Hold successfully placed ${result}`);
103
104                 } else {
105                     holdResult.success = false;
106                     console.info('Hold request failed: ', result);
107
108                     if (Array.isArray(result)) { result = result[0]; }
109
110                     if (this.evt.parse(result)) {
111                         holdResult.evt = this.evt.parse(result);
112                     } else {
113                         holdResult.evt = this.evt.parse(result.last_event);
114                     }
115                 }
116
117                 request.result = holdResult;
118                 return request;
119             }
120         ));
121     }
122
123     getHoldTargetMeta(holdType: string, holdTarget: number | number[],
124         orgId?: number): Observable<HoldRequestTarget> {
125
126         const targetIds = [].concat(holdTarget);
127
128         return this.net.request(
129             'open-ils.circ',
130             'open-ils.circ.hold.get_metadata',
131             holdType, targetIds, orgId
132         ).pipe(mergeMap(meta => {
133             const target: HoldRequestTarget = meta;
134             target.bibId = target.bibrecord.id();
135             target.callNum = meta.volume; // map to client terminology
136
137             return this.bib.getBibSummary(target.bibId)
138             .pipe(map(sum => {
139                 target.bibSummary = sum;
140                 return target;
141             }));
142         }));
143     }
144
145     /**
146       * Update a list of holds.
147       * Returns observable of results, one per hold.
148       * Result is either a Number (hold ID) or an EgEvent object.
149       */
150     updateHolds(holds: IdlObject[]): Observable<any> {
151
152         return this.net.request(
153             'open-ils.circ',
154             'open-ils.circ.hold.update.batch',
155             this.auth.token(), holds
156         ).pipe(map(response => {
157
158             if (Number(response) > 0) { return Number(response); }
159
160             if (Array.isArray(response)) { response = response[0]; }
161
162             const evt = this.evt.parse(response);
163
164             console.warn('Hold update returned event', evt);
165             return evt;
166         }));
167     }
168 }
169
170
171