]> git.evergreen-ils.org Git - Evergreen.git/blob - Open-ILS/src/eg2/src/app/staff/share/marc-edit/tagtable.service.ts
LP1929741 ACQ Selection List & PO Angluar Port
[Evergreen.git] / Open-ILS / src / eg2 / src / app / staff / share / marc-edit / tagtable.service.ts
1 import {Injectable, EventEmitter} from '@angular/core';
2 import {Observable} from 'rxjs';
3 import {map, tap, distinct} from 'rxjs/operators';
4 import {StoreService} from '@eg/core/store.service';
5 import {IdlObject} from '@eg/core/idl.service';
6 import {AuthService} from '@eg/core/auth.service';
7 import {NetService} from '@eg/core/net.service';
8 import {PcrudService} from '@eg/core/pcrud.service';
9 import {ContextMenuEntry} from '@eg/share/context-menu/context-menu.service';
10 import {MARC_RECORD_TYPE} from './editor-context';
11
12 const DEFAULT_MARC_FORMAT = 'marc21';
13
14 interface TagTableSelector {
15     marcFormat?: string;
16     marcRecordType: MARC_RECORD_TYPE;
17
18     // MARC record fixed field "Type" value.
19     ffType: string;
20 }
21
22 export class TagTable {
23
24     store: StoreService;
25     auth: AuthService;
26     net: NetService;
27     pcrud: PcrudService;
28
29     selector: TagTableSelector;
30
31     // Current set of tags in list and map form.
32     tagMap: {[tag: string]: any};
33     ffPosTable: any;
34     ffValueTable: any;
35     fieldTags: ContextMenuEntry[];
36
37     // Cache of compiled, sorted, munged data.  Stuff the UI requests
38     // frequently for selectors, etc.
39     cache: {[valueType: string]: {[which: string]: any}} = {
40         indicators: {},
41         sfcodes: {},
42         sfvalues: {},
43         ffvalues: {}
44     };
45
46     constructor(
47         store: StoreService,
48         auth: AuthService,
49         net: NetService,
50         pcrud: PcrudService,
51         selector: TagTableSelector
52     ) {
53         this.store = store;
54         this.auth = auth;
55         this.net = net;
56         this.pcrud = pcrud;
57         this.selector = selector;
58     }
59
60
61     load(): Promise<any> {
62         return Promise.all([
63             this.loadTagTable(),
64             this.getFfPosTable(),
65             this.getFfValueTable(),
66         ]);
67     }
68
69     // Various data needs munging for display.  Cached the modified
70     // values since they are refernced repeatedly by the UI code.
71     fromCache(dataType: string, which?: string, which2?: string): ContextMenuEntry[] {
72         const part1 = this.cache[dataType][which];
73         if (which2) {
74             if (part1) {
75                 return part1[which2];
76             }
77         } else {
78             return part1;
79         }
80     }
81
82     toCache(dataType: string, which: string,
83         which2: string, values: ContextMenuEntry[]): ContextMenuEntry[] {
84         const base = this.cache[dataType];
85         const part1 = base[which];
86
87         if (which2) {
88             if (!base[which]) { base[which] = {}; }
89             base[which][which2] = values;
90         } else {
91             base[which] = values;
92         }
93
94         return values;
95     }
96
97     getFfPosTable(): Promise<any> {
98         const storeKey = 'FFPosTable_' + this.selector.ffType;
99
100         if (this.ffPosTable) {
101             return Promise.resolve(this.ffPosTable);
102         }
103
104         this.ffPosTable = this.store.getLocalItem(storeKey);
105
106         if (this.ffPosTable) {
107             return Promise.resolve(this.ffPosTable);
108         }
109
110         return this.net.request(
111             'open-ils.fielder', 'open-ils.fielder.cmfpm.atomic',
112             {query: {tag: {'!=' : '006'}, rec_type: this.selector.ffType}}
113
114         ).toPromise().then(table => {
115             this.store.setLocalItem(storeKey, table);
116             return this.ffPosTable = table;
117         });
118     }
119
120     // ffType is the fixed field Type value. BKS, AUT, etc.
121     // See config.marc21_rec_type_map
122     getFfValueTable(): Promise<any> {
123
124         const storeKey = 'FFValueTable_' + this.selector.ffType;
125
126         if (this.ffValueTable) {
127             return Promise.resolve(this.ffValueTable);
128         }
129
130         this.ffValueTable = this.store.getLocalItem(storeKey);
131
132         if (this.ffValueTable) {
133             return Promise.resolve(this.ffValueTable);
134         }
135
136         return this.net.request(
137             'open-ils.cat',
138             'open-ils.cat.biblio.fixed_field_values.by_rec_type',
139             this.selector.ffType
140
141         ).toPromise().then(table => {
142             this.store.setLocalItem(storeKey, table);
143             return this.ffValueTable = table;
144         });
145     }
146
147     loadTagTable(): Promise<any> {
148
149         const sel = this.selector;
150
151         const cacheKey =
152             `current_tag_table_${sel.marcFormat}_${sel.marcRecordType}`;
153
154         this.tagMap = this.store.getLocalItem(cacheKey);
155
156         if (this.tagMap) {
157             return Promise.resolve(this.tagMap);
158         }
159
160         return this.fetchTagTable().then(_ => {
161             this.store.setLocalItem(cacheKey, this.tagMap);
162             return this.tagMap;
163         });
164     }
165
166     fetchTagTable(): Promise<any> {
167         this.tagMap = [];
168         return this.net.request(
169             'open-ils.cat',
170             'open-ils.cat.tag_table.all.retrieve.local',
171             this.auth.token(), this.selector.marcFormat,
172             this.selector.marcRecordType
173         ).pipe(tap(tagData => {
174             this.tagMap[tagData.tag] = tagData;
175         })).toPromise();
176     }
177
178     getSubfieldCodes(tag: string): ContextMenuEntry[] {
179         if (!tag || !this.tagMap[tag]) { return null; }
180
181         const cached = this.fromCache('sfcodes', tag);
182
183         const list = this.tagMap[tag].subfields.map(sf => ({
184             value: sf.code,
185             label: `${sf.code}: ${sf.description}`
186         }))
187         .sort((a, b) => a.label < b.label ? -1 : 1);
188
189         return this.toCache('sfcodes', tag, null, list);
190     }
191
192     getSubfieldLabel(tag: string, sfCode: string): string {
193         if (!tag || !this.tagMap[tag]) { return null; }
194         const subfieldResults = this.tagMap[tag].subfields.filter(sf => sf.code === sfCode);
195         return subfieldResults.length ? subfieldResults[0].description : null;
196     }
197
198
199     getFieldTags(): ContextMenuEntry[] {
200
201         if (!this.fieldTags) {
202             this.fieldTags = Object.keys(this.tagMap)
203             .filter(tag => Boolean(this.tagMap[tag]))
204             .map(tag => ({
205                 value: tag,
206                 label: `${tag}: ${this.tagMap[tag].name}`
207             }))
208             .sort((a, b) => a.label < b.label ? -1 : 1);
209         }
210
211         return this.fieldTags;
212     }
213
214     getSubfieldValues(tag: string, sfCode: string): ContextMenuEntry[] {
215         if (!tag || !this.tagMap[tag]) { return []; }
216
217         const cached = this.fromCache('sfvalues', tag, sfCode);
218         if (cached) { return cached; }
219
220         const list: ContextMenuEntry[] = [];
221
222         this.tagMap[tag].subfields
223         .filter(sf =>
224             sf.code === sfCode && sf.hasOwnProperty('value_list'))
225         .forEach(sf => {
226             sf.value_list.forEach(value => {
227
228                 let label = value.description || value.code;
229                 const code = value.code || label;
230                 if (code !== label) { label = `${code}: ${label}`; }
231
232                 list.push({value: code, label: label});
233             });
234         });
235
236         return this.toCache('sfvalues', tag, sfCode, list);
237     }
238
239     getIndicatorValues(tag: string, which: 'ind1' | 'ind2'): ContextMenuEntry[] {
240         if (!tag || !this.tagMap[tag]) { return; }
241
242         const cached = this.fromCache('indicators', tag, which);
243         if (cached) { return cached; }
244
245         let values = this.tagMap[tag][which];
246         if (!values) { return; }
247
248         values = values.map(value => ({
249             value: value.code,
250             label: `${value.code}: ${value.description}`
251         }))
252         .sort((a, b) => a.label < b.label ? -1 : 1);
253
254         return this.toCache('indicators', tag, which, values);
255     }
256
257
258     getFfFieldMeta(fieldCode: string): Promise<IdlObject> {
259         return this.getFfPosTable().then(table => {
260
261             // Best I can tell, the AngJS MARC editor stores the
262             // full POS table for all record types in every copy of
263             // the table, hence the seemingly extraneous check in ffType.
264             return table.filter(
265                 field =>
266                     field.fixed_field === fieldCode
267                  && field.rec_type === this.selector.ffType
268             )[0];
269         });
270     }
271
272
273     // Assumes getFfPosTable and getFfValueTable have already been
274     // invoked for the requested record type.
275     getFfValues(fieldCode: string): ContextMenuEntry[] {
276
277         const cached = this.fromCache('ffvalues', fieldCode);
278         if (cached) { return cached; }
279
280         let values = this.ffValueTable;
281
282         if (!values || !values[fieldCode]) { return null; }
283
284         // extract the canned set of possible values for our
285         // fixed field.  Ignore those whose value exceeds the
286         // specified field length.
287         values = values[fieldCode]
288             .filter(val => val[0].length <= val[2])
289             .map(val => ({value: val[0], label: `${val[0]}: ${val[1]}`}))
290             .sort((a, b) => a.label < b.label ? -1 : 1);
291
292         return this.toCache('ffvalues', fieldCode, null, values);
293     }
294
295 }
296
297 @Injectable()
298 export class TagTableService {
299
300     tagTables: {[marcRecordType: string]: TagTable} = {};
301     controlledBibTags: string[];
302
303     constructor(
304         private store: StoreService,
305         private auth: AuthService,
306         private net: NetService,
307         private pcrud: PcrudService,
308     ) {}
309
310     loadTags(selector: TagTableSelector): Promise<TagTable> {
311         if (!selector.marcFormat) {
312             selector.marcFormat = DEFAULT_MARC_FORMAT;
313         }
314
315         // Tag tables of a given marc record type are identical.
316         if (this.tagTables[selector.marcRecordType]) {
317             return Promise.resolve(this.tagTables[selector.marcRecordType]);
318         }
319
320         const tt = new TagTable(
321             this.store, this.auth, this.net, this.pcrud, selector);
322
323         this.tagTables[selector.marcRecordType] = tt;
324
325         return tt.load().then(_ => tt);
326     }
327
328     getControlledBibTags(): Promise<string[]> {
329         if (this.controlledBibTags) {
330             return Promise.resolve(this.controlledBibTags);
331         }
332
333         this.controlledBibTags = [];
334         return this.pcrud.retrieveAll('acsbf', {select: ['tag']})
335         .pipe(
336             map(field => field.tag()),
337             distinct(),
338             map(tag => this.controlledBibTags.push(tag))
339         ).toPromise().then(_ => this.controlledBibTags);
340     }
341 }
342
343
344