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 {EventService} from '@eg/core/event.service';
10 import {ContextMenuEntry} from '@eg/share/context-menu/context-menu.service';
12 interface TagTableSelector {
14 marcRecordType?: string;
17 const defaultTagTableSelector: TagTableSelector = {
18 marcFormat : 'marc21',
19 marcRecordType : 'biblio'
23 export class TagTableService {
25 // Current set of tags in list and map form.
26 tagMap: {[tag: string]: any} = {};
27 ffPosMap: {[rtype: string]: any[]} = {};
28 ffValueMap: {[rtype: string]: any} = {};
29 controlledBibTags: string[];
32 {[valueType: string]: {[which: string]: any}} = {};
35 private store: StoreService,
36 private auth: AuthService,
37 private net: NetService,
38 private pcrud: PcrudService,
39 private evt: EventService
42 this.extractedValuesCache = {
51 // Various data needs munging for display. Cached the modified
52 // values since they are refernced repeatedly by the UI code.
53 fromCache(dataType: string, which?: string, which2?: string): ContextMenuEntry[] {
54 const part1 = this.extractedValuesCache[dataType][which];
64 toCache(dataType: string, which: string,
65 which2: string, values: ContextMenuEntry[]): ContextMenuEntry[] {
66 const base = this.extractedValuesCache[dataType];
67 const part1 = base[which];
70 if (!base[which]) { base[which] = {}; }
71 base[which][which2] = values;
79 getFfPosTable(rtype: string): Promise<any> {
80 const storeKey = 'FFPosTable_' + rtype;
82 if (this.ffPosMap[rtype]) {
83 return Promise.resolve(this.ffPosMap[rtype]);
86 this.ffPosMap[rtype] = this.store.getLocalItem(storeKey);
88 if (this.ffPosMap[rtype]) {
89 return Promise.resolve(this.ffPosMap[rtype]);
92 return this.net.request(
93 'open-ils.fielder', 'open-ils.fielder.cmfpm.atomic',
94 {query: {tag: {'!=' : '006'}, rec_type: rtype}}
96 ).toPromise().then(table => {
97 this.store.setLocalItem(storeKey, table);
98 return this.ffPosMap[rtype] = table;
102 getFfValueTable(rtype: string): Promise<any> {
104 const storeKey = 'FFValueTable_' + rtype;
106 if (this.ffValueMap[rtype]) {
107 return Promise.resolve(this.ffValueMap[rtype]);
110 this.ffValueMap[rtype] = this.store.getLocalItem(storeKey);
112 if (this.ffValueMap[rtype]) {
113 return Promise.resolve(this.ffValueMap[rtype]);
116 return this.net.request(
118 'open-ils.cat.biblio.fixed_field_values.by_rec_type', rtype
120 ).toPromise().then(table => {
121 this.store.setLocalItem(storeKey, table);
122 return this.ffValueMap[rtype] = table;
126 loadTagTable(selector?: TagTableSelector): Promise<any> {
129 if (!selector.marcFormat) {
130 selector.marcFormat = defaultTagTableSelector.marcFormat;
132 if (!selector.marcRecordType) {
133 selector.marcRecordType =
134 defaultTagTableSelector.marcRecordType;
137 selector = defaultTagTableSelector;
141 `current_tag_table_${selector.marcFormat}_${selector.marcRecordType}`;
143 this.tagMap = this.store.getLocalItem(cacheKey);
146 return Promise.resolve(this.tagMap);
149 return this.fetchTagTable(selector).then(_ => {
150 this.store.setLocalItem(cacheKey, this.tagMap);
155 fetchTagTable(selector?: TagTableSelector): Promise<any> {
157 return this.net.request(
159 'open-ils.cat.tag_table.all.retrieve.local',
160 this.auth.token(), selector.marcFormat, selector.marcRecordType
161 ).pipe(tap(tagData => {
162 this.tagMap[tagData.tag] = tagData;
166 getSubfieldCodes(tag: string): ContextMenuEntry[] {
167 if (!tag || !this.tagMap[tag]) { return null; }
169 const cached = this.fromCache('sfcodes', tag);
171 const list = this.tagMap[tag].subfields.map(sf => ({
173 label: `${sf.code}: ${sf.description}`
175 .sort((a, b) => a.label < b.label ? -1 : 1);
177 return this.toCache('sfcodes', tag, null, list);
180 getFieldTags(): ContextMenuEntry[] {
182 const cached = this.fromCache('fieldtags');
183 if (cached) { return cached; }
185 return Object.keys(this.tagMap)
186 .filter(tag => Boolean(this.tagMap[tag]))
189 label: `${tag}: ${this.tagMap[tag].name}`
191 .sort((a, b) => a.label < b.label ? -1 : 1);
194 getSubfieldValues(tag: string, sfCode: string): ContextMenuEntry[] {
195 if (!tag || !this.tagMap[tag]) { return []; }
197 const cached = this.fromCache('sfvalues', tag, sfCode);
198 if (cached) { return cached; }
200 const list: ContextMenuEntry[] = [];
202 this.tagMap[tag].subfields
204 sf.code === sfCode && sf.hasOwnProperty('value_list'))
206 sf.value_list.forEach(value => {
208 let label = value.description || value.code;
209 const code = value.code || label;
210 if (code !== label) { label = `${code}: ${label}`; }
212 list.push({value: code, label: label});
216 return this.toCache('sfvalues', tag, sfCode, list);
219 getIndicatorValues(tag: string, which: 'ind1' | 'ind2'): ContextMenuEntry[] {
220 if (!tag || !this.tagMap[tag]) { return; }
222 const cached = this.fromCache('indicators', tag, which);
223 if (cached) { return cached; }
225 let values = this.tagMap[tag][which];
226 if (!values) { return; }
228 values = values.map(value => ({
230 label: `${value.code}: ${value.description}`
232 .sort((a, b) => a.label < b.label ? -1 : 1);
234 return this.toCache('indicators', tag, which, values);
238 getFfFieldMeta(fieldCode: string, recordType: string): Promise<IdlObject> {
239 return this.getFfPosTable(recordType).then(table => {
241 // Note the AngJS MARC editor stores the full POS table
242 // for all record types in every copy of the table, hence
243 // the seemingly extraneous check in recordType.
246 field.fixed_field === fieldCode
247 && field.rec_type === recordType
253 // Assumes getFfPosTable and getFfValueTable have already been
254 // invoked for the request record type.
255 getFfValues(fieldCode: string, recordType: string): ContextMenuEntry[] {
257 const cached = this.fromCache('ffvalues', recordType, fieldCode);
258 if (cached) { return cached; }
260 let values = this.ffValueMap[recordType];
262 if (!values || !values[fieldCode]) { return null; }
264 // extract the canned set of possible values for our
265 // fixed field. Ignore those whose value exceeds the
266 // specified field length.
267 values = values[fieldCode]
268 .filter(val => val[0].length <= val[2])
269 .map(val => ({value: val[0], label: `${val[0]}: ${val[1]}`}))
270 .sort((a, b) => a.label < b.label ? -1 : 1);
272 return this.toCache('ffvalues', recordType, fieldCode, values);
275 getControlledBibTags(): Promise<string[]> {
276 if (this.controlledBibTags) {
277 return Promise.resolve(this.controlledBibTags);
280 this.controlledBibTags = [];
281 return this.pcrud.retrieveAll('acsbf', {select: ['tag']})
283 map(field => field.tag()),
285 map(tag => this.controlledBibTags.push(tag))
286 ).toPromise().then(_ => this.controlledBibTags);