]> git.evergreen-ils.org Git - Evergreen.git/blob - Open-ILS/src/eg2/src/app/staff/acq/lineitem/lineitem.service.ts
LP1929741 ACQ Selection List & PO Angluar Port
[Evergreen.git] / Open-ILS / src / eg2 / src / app / staff / acq / lineitem / lineitem.service.ts
1 import {Injectable, EventEmitter} from '@angular/core';
2 import {Observable, from, concat, empty} from 'rxjs';
3 import {switchMap, map, tap, merge} from 'rxjs/operators';
4 import {IdlObject, IdlService} from '@eg/core/idl.service';
5 import {NetService} from '@eg/core/net.service';
6 import {AuthService} from '@eg/core/auth.service';
7 import {PcrudService} from '@eg/core/pcrud.service';
8 import {ComboboxEntry} from '@eg/share/combobox/combobox.component';
9 import {ItemLocationService} from '@eg/share/item-location-select/item-location-select.service';
10
11 export interface BatchLineitemStruct {
12     id: number;
13     lineitem: IdlObject;
14     existing_copies: number;
15     all_locations: IdlObject[];
16     all_funds: IdlObject[];
17     all_circ_modifiers: IdlObject[];
18 }
19
20 export interface BatchLineitemUpdateStruct {
21     lineitem: IdlObject;
22     lid: number;
23     max: number;
24     progress: number;
25     complete: number; // Perl bool
26     total: number;
27     [key: string]: any; // Perl Acq::BatchManager
28 }
29
30 interface FleshedLineitemParams {
31
32     // Flesh data beyond the default.
33     fleshMore?: any;
34
35     // OK to pull the requested LI from the cache.
36     fromCache?: boolean;
37
38     // OK to add this LI to the cache.
39     // Generally a good thing, but if you are fetching an LI with
40     // fewer fleshed fields than the default, this could break code.
41     toCache?: boolean;
42 }
43
44 @Injectable()
45 export class LineitemService {
46
47     liAttrDefs: IdlObject[];
48
49     // Emitted when our copy batch editor wants to apply a value
50     // to a set of inputs.  This allows the the copy input comboboxes, etc.
51     // to add the entry before it's forced to grab the value from the
52     // server, often in large parallel batches.
53     batchOptionWanted: EventEmitter<{[field: string]: ComboboxEntry}>
54         = new EventEmitter<{[field: string]: ComboboxEntry}> ();
55
56     // Emits a LI ID if the LI was edited in a way that could impact
57     // its activatability of its linked PO.
58     activateStateChange: EventEmitter<number> = new EventEmitter<number>();
59
60     // Cache for circ modifiers and funds; locations are cached in the
61     // item location select service.
62     circModCache: {[code: string]: IdlObject} = {};
63     fundCache: {[id: number]: IdlObject} = {};
64     liCache: {[id: number]: BatchLineitemStruct} = {};
65
66     // Alerts the user has already confirmed are OK.
67     alertAcks: {[id: number]: boolean} = {};
68
69     constructor(
70         private idl: IdlService,
71         private net: NetService,
72         private auth: AuthService,
73         private pcrud: PcrudService,
74         private loc: ItemLocationService
75     ) {}
76
77     getFleshedLineitems(ids: number[],
78         params: FleshedLineitemParams = {}): Observable<BatchLineitemStruct> {
79
80         if (params.fromCache) {
81             const fromCache = this.getLineitemsFromCache(ids);
82             if (fromCache) { return from(fromCache); }
83         }
84
85         const flesh: any = Object.assign({
86             flesh_attrs: true,
87             flesh_provider: true,
88             flesh_order_summary: true,
89             flesh_cancel_reason: true,
90             flesh_li_details: true,
91             flesh_notes: true,
92             flesh_fund: true,
93             flesh_circ_modifier: true,
94             flesh_location: true,
95             flesh_fund_debit: true,
96             flesh_po: true,
97             flesh_pl: true,
98             flesh_formulas: true,
99             flesh_copies: true,
100             clear_marc: false
101         }, params.fleshMore || {});
102
103         return this.net.request(
104             'open-ils.acq', 'open-ils.acq.lineitem.retrieve.batch',
105             this.auth.token(), ids, flesh
106         ).pipe(tap(liStruct =>
107             this.ingestLineitem(liStruct, params.toCache)));
108     }
109
110     getLineitemsFromCache(ids: number[]): BatchLineitemStruct[] {
111
112         const fromCache: BatchLineitemStruct[] = [];
113
114         ids.forEach(id => {
115             if (this.liCache[id]) { fromCache.push(this.liCache[id]); }
116         });
117
118         // Only return LI's from cache if all of the requested LI's
119         // are cached, otherwise they would be returned in the wrong
120         // order.  Typically it will be all or none so I'm not
121         // fussing with interleaving cached and uncached lineitems
122         // to fix the sorting.
123         if (fromCache.length === ids.length) { return fromCache; }
124
125         return null;
126     }
127
128     ingestLineitem(
129         liStruct: BatchLineitemStruct, toCache: boolean): BatchLineitemStruct {
130
131         const li = liStruct.lineitem;
132
133         // These values come through as NULL
134         const summary = li.order_summary();
135         if (!summary.estimated_amount()) { summary.estimated_amount(0); }
136         if (!summary.encumbrance_amount()) { summary.encumbrance_amount(0); }
137         if (!summary.paid_amount()) { summary.paid_amount(0); }
138
139         // Sort the formula applications
140         li.distribution_formulas(
141             li.distribution_formulas().sort((f1, f2) =>
142                 f1.create_time() < f2.create_time() ? -1 : 1)
143         );
144
145         // consistent sorting
146         li.lineitem_details(
147             li.lineitem_details().sort((d1, d2) =>
148                 d1.id() < d2.id() ? -1 : 1)
149         );
150
151         // De-flesh some values we don't want living directly on
152         // the copy.  Cache the values so our comboboxes, etc.
153         // can use them without have to re-fetch them .
154         li.lineitem_details().forEach(copy => {
155             let val;
156             if ((val = copy.circ_modifier())) { // assignment
157                 this.circModCache[val.code()] = copy.circ_modifier();
158                 copy.circ_modifier(val.code());
159             }
160             if ((val = copy.fund())) {
161                 this.fundCache[val.id()] = copy.fund();
162                 copy.fund(val.id());
163             }
164             if ((val = copy.location())) {
165                 this.loc.locationCache[val.id()] = copy.location();
166                 copy.location(val.id());
167             }
168         });
169
170         if (toCache) { this.liCache[li.id()] = liStruct; }
171         return liStruct;
172     }
173
174     // Returns all matching attributes
175     // 'li' should be fleshed with attributes()
176     getAttributes(li: IdlObject, name: string, attrType?: string): IdlObject[] {
177         const values: IdlObject[] = [];
178         li.attributes().forEach(attr => {
179             if (attr.attr_name() === name) {
180                 if (!attrType || attrType === attr.attr_type()) {
181                     values.push(attr);
182                 }
183             }
184         });
185
186         return values;
187     }
188
189     getAttributeValues(li: IdlObject, name: string, attrType?: string): string[] {
190         return this.getAttributes(li, name, attrType).map(attr => attr.attr_value());
191     }
192
193     // Returns the first matching attribute
194     // 'li' should be fleshed with attributes()
195     getFirstAttribute(li: IdlObject, name: string, attrType?: string): IdlObject {
196         return this.getAttributes(li, name, attrType)[0];
197     }
198
199     getFirstAttributeValue(li: IdlObject, name: string, attrType?: string): string {
200         const attr = this.getFirstAttribute(li, name, attrType);
201         return attr ? attr.attr_value() : '';
202     }
203
204     getOrderIdent(li: IdlObject): IdlObject {
205         for (let idx = 0; idx < li.attributes().length; idx++) {
206             const attr = li.attributes()[idx];
207             if (attr.order_ident() === 't' &&
208                 attr.attr_type() === 'lineitem_local_attr_definition') {
209                 return attr;
210             }
211         }
212         return null;
213     }
214
215     // Returns an updated copy of the lineitem
216     changeOrderIdent(li: IdlObject,
217         id: number, attrType: string, attrValue: string): Observable<IdlObject> {
218
219         const args: any = {lineitem_id: li.id()};
220
221         if (id) {
222             // Order ident set to an existing attribute.
223             args.source_attr_id = id;
224         } else {
225             // Order ident set to a new free text value
226             args.attr_name = attrType;
227             args.attr_value = attrValue;
228         }
229
230         return this.net.request(
231             'open-ils.acq',
232             'open-ils.acq.lineitem.order_identifier.set',
233             this.auth.token(), args
234         ).pipe(switchMap(_ => this.getFleshedLineitems([li.id()]))
235         ).pipe(map(struct => struct.lineitem));
236     }
237
238     applyBatchNote(liIds: number[],
239         noteValue: string, vendorPublic: boolean): Promise<any> {
240
241         if (!noteValue || liIds.length === 0) { return Promise.resolve(); }
242
243         const notes = [];
244         liIds.forEach(id => {
245             const note = this.idl.create('acqlin');
246             note.isnew(true);
247             note.lineitem(id);
248             note.value(noteValue);
249             note.vendor_public(vendorPublic ? 't' : 'f');
250             notes.push(note);
251         });
252
253         return this.net.request('open-ils.acq',
254             'open-ils.acq.lineitem_note.cud.batch',
255             this.auth.token(), notes
256         ).pipe(tap(resp => {
257             if (resp && resp.note) {
258                 const li = this.liCache[resp.note.lineitem()].lineitem;
259                 li.lineitem_notes().unshift(resp.note);
260             }
261         })).toPromise();
262     }
263
264     getLiAttrDefs(): Promise<IdlObject[]> {
265         if (this.liAttrDefs) {
266             return Promise.resolve(this.liAttrDefs);
267         }
268
269         return this.pcrud.retrieveAll('acqliad', {}, {atomic: true})
270         .toPromise().then(defs => this.liAttrDefs = defs);
271     }
272
273     updateLiDetails(li: IdlObject): Observable<BatchLineitemUpdateStruct> {
274         const lids = li.lineitem_details().filter(copy =>
275             (copy.isnew() || copy.ischanged() || copy.isdeleted()));
276
277         return this.net.request(
278             'open-ils.acq',
279             'open-ils.acq.lineitem_detail.cud.batch', this.auth.token(), lids);
280     }
281
282     updateLineitems(lis: IdlObject[]): Observable<BatchLineitemUpdateStruct> {
283
284         // Fire updates one LI at a time.  Note the API allows passing
285         // multiple LI's, but does not stream responses.  This approach
286         // allows the caller to get a stream of responses instead of a
287         // final "all done".
288         let obs: Observable<any> = empty();
289         lis.forEach(li => {
290             obs = concat(obs, this.net.request(
291                 'open-ils.acq',
292                 'open-ils.acq.lineitem.update',
293                 this.auth.token(), li
294             ));
295         });
296
297         return obs;
298     }
299 }
300