]> git.evergreen-ils.org Git - working/Evergreen.git/blob - Open-ILS/src/eg2/src/app/staff/share/marc-edit/tagtable.service.ts
LP1852782 Angular MARC enriched editor (first batch)
[working/Evergreen.git] / Open-ILS / src / eg2 / src / app / staff / share / marc-edit / tagtable.service.ts
1 import {Injectable, EventEmitter} from '@angular/core';
2 import {map, tap} from 'rxjs/operators';
3 import {StoreService} from '@eg/core/store.service';
4 import {IdlObject} from '@eg/core/idl.service';
5 import {AuthService} from '@eg/core/auth.service';
6 import {NetService} from '@eg/core/net.service';
7 import {PcrudService} from '@eg/core/pcrud.service';
8 import {EventService} from '@eg/core/event.service';
9 import {ContextMenuEntry} from '@eg/share/context-menu/context-menu.service';
10
11 interface TagTableSelector {
12     marcFormat?: string;
13     marcRecordType?: string;
14 }
15
16 const defaultTagTableSelector: TagTableSelector = {
17     marcFormat     : 'marc21',
18     marcRecordType : 'biblio'
19 }
20
21 @Injectable()
22 export class TagTableService {
23
24     // Current set of tags in list and map form.
25     tagMap: {[tag: string]: any} = {};
26     ffPosMap: {[rtype: string]: any[]} = {};
27     ffValueMap: {[rtype: string]: any} = {};
28
29     extractedValuesCache: 
30         {[valueType: string]: {[which: string]: any}} = {};
31
32     constructor(
33         private store: StoreService,
34         private auth: AuthService,
35         private net: NetService,
36         private pcrud: PcrudService,
37         private evt: EventService
38     ) {
39
40         this.extractedValuesCache = {
41             fieldtags: {},
42             indicators: {},
43             sfcodes: {},
44             sfvalues: {},
45             ffvalues: {}
46         };
47     }
48
49     // Various data needs munging for display.  Cached the modified
50     // values since they are refernced repeatedly by the UI code.
51     fromCache(dataType: string, which?: string, which2?: string): ContextMenuEntry[] {
52         const part1 = this.extractedValuesCache[dataType][which];
53         if (which2) {
54             if (part1) {
55                 return part1[which2];
56             }
57         } else {
58             return part1;
59         }
60     }
61
62     toCache(dataType: string, which: string, 
63         which2: string, values: ContextMenuEntry[]): ContextMenuEntry[] {
64         const base = this.extractedValuesCache[dataType];
65         const part1 = base[which];
66
67         if (which2) {
68             if (!base[which]) { base[which] = {}; }
69             base[which][which2] = values;
70         } else {
71             base[which] = values;
72         }
73
74         return values;
75     }
76
77     getFfPosTable(rtype: string): Promise<any> {
78         const storeKey = 'FFPosTable_' + rtype;
79
80         if (this.ffPosMap[rtype]) {
81             return Promise.resolve(this.ffPosMap[rtype]);
82         }
83
84         this.ffPosMap[rtype] = this.store.getLocalItem(storeKey);
85
86         if (this.ffPosMap[rtype]) {
87             return Promise.resolve(this.ffPosMap[rtype]);
88         }
89
90         return this.net.request(
91             'open-ils.fielder', 'open-ils.fielder.cmfpm.atomic',
92             {query: {tag: {'!=' : '006'}, rec_type: rtype}}
93
94         ).toPromise().then(table => {
95             this.store.setLocalItem(storeKey, table);
96             return this.ffPosMap[rtype] = table;
97         });
98     }
99
100     getFfValueTable(rtype: string): Promise<any> {
101
102         const storeKey = 'FFValueTable_' + rtype;
103
104         if (this.ffValueMap[rtype]) {
105             return Promise.resolve(this.ffValueMap[rtype]);
106         }
107
108         this.ffValueMap[rtype] = this.store.getLocalItem(storeKey);
109
110         if (this.ffValueMap[rtype]) {
111             return Promise.resolve(this.ffValueMap[rtype]);
112         }
113
114         return this.net.request(
115             'open-ils.cat',
116             'open-ils.cat.biblio.fixed_field_values.by_rec_type', rtype
117
118         ).toPromise().then(table => {
119             this.store.setLocalItem(storeKey, table);
120             return this.ffValueMap[rtype] = table;
121         });
122     }
123
124     loadTagTable(selector?: TagTableSelector): Promise<any> {
125
126         if (selector) {
127             if (!selector.marcFormat) {
128                 selector.marcFormat = defaultTagTableSelector.marcFormat;
129             }
130             if (!selector.marcRecordType) {
131                 selector.marcRecordType = 
132                     defaultTagTableSelector.marcRecordType;
133             }
134         } else {
135             selector = defaultTagTableSelector;
136         }
137
138         const cacheKey = 'FFValueTable_' + selector.marcRecordType;
139
140         this.tagMap = this.store.getLocalItem(cacheKey);
141
142         if (this.tagMap) {
143             return Promise.resolve(this.tagMap);
144         }
145
146         return this.fetchTagTable(selector).then(_ => {
147             this.store.setLocalItem(cacheKey, this.tagMap);
148             return this.tagMap;
149         });
150     }
151
152     fetchTagTable(selector?: TagTableSelector): Promise<any> {
153         this.tagMap = [];
154         return this.net.request(
155             'open-ils.cat',
156             'open-ils.cat.tag_table.all.retrieve.local',
157             this.auth.token(), selector.marcFormat, selector.marcRecordType
158         ).pipe(tap(tagData => {
159             this.tagMap[tagData.tag] = tagData;
160         })).toPromise();
161     }
162
163     getSubfieldCodes(tag: string): ContextMenuEntry[] { 
164         if (!tag || !this.tagMap[tag]) { return null; }
165
166         const cached = this.fromCache('sfcodes', tag);
167
168         const list = this.tagMap[tag].subfields.map(sf => ({
169             value: sf.code, 
170             label: `${sf.code}: ${sf.description}`
171         })) 
172         .sort((a, b) => a.label < b.label ? -1 : 1);
173
174         return this.toCache('sfcodes', tag, null, list);
175     }
176
177     getFieldTags(): ContextMenuEntry[] {
178
179         const cached = this.fromCache('fieldtags');
180         if (cached) { return cached; }
181         
182         return Object.keys(this.tagMap)
183         .filter(tag => Boolean(this.tagMap[tag]))
184         .map(tag => ({
185             value: tag,
186             label: `${tag}: ${this.tagMap[tag].name}`
187         }))
188         .sort((a, b) => a.label < b.label ? -1 : 1);
189     }
190
191     getSubfieldValues(tag: string, sfCode: string): ContextMenuEntry[] {
192         if (!tag || !this.tagMap[tag]) { return []; }
193
194         const cached = this.fromCache('sfvalues', tag, sfCode)
195         if (cached) { return cached; }
196
197         const list: ContextMenuEntry[] = [];
198
199         this.tagMap[tag].subfields
200         .filter(sf =>
201             sf.code === sfCode && sf.hasOwnProperty('value_list'))
202         .forEach(sf => {
203             sf.value_list.forEach(value => {
204
205                 let label = value.description || value.code;
206                 let code = value.code || label;
207                 if (code !== label) { label = `${code}: ${label}`; }
208
209                 list.push({value: code, label: label});
210             })
211         });
212
213         return this.toCache('sfvalues', tag, sfCode, list);
214     }
215
216     getIndicatorValues(tag: string, which: 'ind1' | 'ind2'): ContextMenuEntry[] {
217         if (!tag || !this.tagMap[tag]) { return }
218
219         const cached = this.fromCache('indicators', tag, which);
220         if (cached) { return cached; }
221
222         let values = this.tagMap[tag][which];
223         if (!values) { return; }
224
225         values = values.map(value => ({
226             value: value.code,
227             label: `${value.code}: ${value.description}`
228         }))
229         .sort((a, b) => a.label < b.label ? -1 : 1);
230
231         return this.toCache('indicators', tag, which, values);
232     }
233
234
235     getFfFieldMeta(fieldCode: string, recordType: string): Promise<IdlObject> {
236         return this.getFfPosTable(recordType).then(table => {
237
238             // Note the AngJS MARC editor stores the full POS table
239             // for all record types in every copy of the table, hence
240             // the seemingly extraneous check in recordType.
241             return table.filter(
242                 field =>
243                     field.fixed_field === fieldCode
244                  && field.rec_type === recordType
245             )[0];
246         });
247     }
248
249
250     // Assumes getFfPosTable and getFfValueTable have already been
251     // invoked for the request record type.
252     getFfValues(fieldCode: string, recordType: string): ContextMenuEntry[] {
253
254         const cached = this.fromCache('ffvalues', recordType, fieldCode);
255         if (cached) { return cached; }
256
257         let values = this.ffValueMap[recordType];
258
259         if (!values || !values[fieldCode]) { return null; }
260
261         // extract the canned set of possible values for our
262         // fixed field.  Ignore those whose value exceeds the
263         // specified field length.
264         values = values[fieldCode]
265             .filter(val => val[0].length <= val[2])
266             .map(val => ({value: val[0], label: `${val[0]}: ${val[1]}`}))
267             .sort((a, b) => a.label < b.label ? -1 : 1);
268
269         return this.toCache('ffvalues', recordType, fieldCode, values);
270     }
271 }
272
273
274