LP#1745462: avoid trying to validate empty fields
[Evergreen.git] / Open-ILS / web / js / ui / default / staff / cat / services / tagtable.js
1 /*
2  * Retrieve, cache, and query MARC tag tables
3  */
4 angular.module('egCoreMod')
5 .factory('egTagTable', 
6        ['$q', 'egCore', 'egAuth',
7 function($q,   egCore,   egAuth) {
8
9     var service = {
10         defaultTagTableSelector : {
11             marcFormat     : 'marc21',
12             marcRecordType : 'biblio',
13         },
14         fields : { },
15         ff_pos_map : { },
16         ff_value_map : { },
17         phys_char_type_map : null,
18         phys_char_sf_map : { },
19         phys_char_value_map : { },
20         authority_control_set : {
21             _remote_loaded : false,
22             _controlsets : [ ]
23         },
24         _active_control_set : undefined
25     };
26
27     service.initialized = function() {
28         return service.authority_control_set._remote_loaded;
29     }
30
31     // allow 'bre' and 'biblio' to be synonyms, etc.
32     service.normalizeRecordType = function(recordType) {
33         if (recordType === 'sre') {
34             return 'serial';
35         } else if (recordType === 'bre') {
36             return 'biblio';
37         } else if (recordType === 'are') {
38             return 'authority';
39         } else {
40             return recordType;
41         }
42     };
43
44     service.loadTagTable = function(args) {
45         var fields = service.defaultTagTableSelector;
46         if (args) {
47             if (args.marcFormat) {
48                 fields.marcFormat = args.marcFormat;
49             }
50             if (args.marcRecordType) {
51                 fields.marcRecordType = service.normalizeRecordType(args.marcRecordType);
52             }
53         }
54         var tt_key = 'current_tag_table_' + fields.marcFormat + '_' +
55                      fields.marcRecordType;
56         egCore.hatch.getItem(tt_key).then(function(tt) {
57             if (tt) {
58                 service.fields = tt;
59             } else {
60                 service.loadRemoteTagTable(fields, tt_key);
61             }
62         });
63     };
64
65     var _ffpos_promises = {};
66     service.fetchFFPosTable = function(rtype) {
67         if (!(rtype in _ffpos_promises)) {
68             _ffpos_promises[rtype] = $q.defer();
69
70             var hatch_pos_key = 'FFPosTable_'+rtype;
71
72             egCore.hatch.getItem(hatch_pos_key).then(function(cached_table) {
73                 if (cached_table) {
74                     service.ff_pos_map[rtype] = cached_table;
75                     _ffpos_promises[rtype].resolve(cached_table);
76
77                 } else {
78
79                     egCore.net.request( // First, get the list of FFs (minus 006)
80                         'open-ils.fielder',
81                         'open-ils.fielder.cmfpm.atomic',
82                         { query : { tag : { '!=' : '006' } } }
83                     ).then(function (data)  {
84                         service.ff_pos_map[rtype] = data;
85                         egCore.hatch.setItem(hatch_pos_key, data);
86                         _ffpos_promises[rtype].resolve(data);
87                     });
88                 }
89             });
90         }
91
92         return _ffpos_promises[rtype].promise;
93     };
94
95     var _ffval_promises = {};
96     service.fetchFFValueTable = function(rtype) {
97         if (!(rtype in _ffval_promises)) {
98             _ffval_promises[rtype] = $q.defer();
99
100             var hatch_value_key = 'FFValueTable_'+rtype;
101
102             egCore.hatch.getItem(hatch_value_key).then(function(cached_table) {
103                 if (cached_table) {
104                     service.ff_value_map[rtype] = cached_table;
105                     _ffval_promises[rtype].resolve(cached_table);
106
107                 } else {
108
109                     egCore.net.request(
110                         'open-ils.cat',
111                         'open-ils.cat.biblio.fixed_field_values.by_rec_type',
112                         rtype
113                     ).then(function (data)  {
114                         service.ff_value_map[rtype] = data;
115                         egCore.hatch.setItem(hatch_value_key, data);
116                         _ffval_promises[rtype].resolve(data);
117                     });
118                 }
119             });
120         }
121
122         return _ffval_promises[rtype].promise;
123     };
124
125     service.loadRemoteTagTable = function(fields, tt_key) {
126         egCore.net.request(
127             'open-ils.cat',
128             'open-ils.cat.tag_table.all.retrieve.local',
129             egAuth.token(), fields.marcFormat, fields.marcRecordType
130         ).then(
131             function (data)  {
132                 egCore.hatch.setItem(tt_key, service.fields);
133             },
134             function (err)   { console.err('error fetch tag table: ' + err) }, 
135             function (field) {
136                 if (!field) return;
137                 service.fields[field.tag] = field;
138             }
139         );
140     };
141
142     service.getFieldTags = function() {
143         var list = [];
144         angular.forEach(service.fields, function(value, key) {
145             this.push({ 
146                 value: key,
147                 label: key + ': ' + value.name
148             });
149         }, list);
150         return list;
151     }
152
153     service.getSubfieldCodes = function(tag) {
154         var list = [];
155         if (!tag) return;
156         if (!service.fields[tag]) return;
157         angular.forEach(service.fields[tag].subfields, function(value) {
158             this.push({
159                 value: value.code,
160                 label: value.code + ': ' + value.description
161             });
162         }, list);
163         return list;
164     }
165
166     service.getSubfieldValues = function(tag, sf_code) {
167         var list = [];
168         if (!tag) return list;
169         if (!service.fields[tag]) return;
170         if (!service.fields[tag]) return;
171         angular.forEach(service.fields[tag].subfields, function(sf) {
172             if (sf.code == sf_code && sf.hasOwnProperty('value_list')) {
173                 angular.forEach(sf.value_list, function(value) {
174                     var label = (value.code == value.description) ?
175                                 value.code :
176                                 value.code + ': ' + value.description;
177                     this.push({
178                         value: value.code,
179                         label: label
180                     });
181                 }, this);
182             }
183         }, list);
184         return list;
185     }
186
187     service.getIndicatorValues = function(tag, pos) {
188         var list = [];
189         if (!tag) return list;
190         if (!service.fields[tag]) return;
191         if (!service.fields[tag]["ind" + pos]) return;
192         angular.forEach(service.fields[tag]["ind" + pos], function(value) {
193             this.push({
194                 value: value.code,
195                 label: value.code + ': ' + value.description
196             });
197         }, list);
198         return list;
199     }
200
201     service.authorityControlSet = function (kwargs) {
202     
203         kwargs = kwargs || {};
204
205         this._fetch_class = function(hint, cache_key) {
206             return egCore.pcrud.retrieveAll(hint, {}, {atomic : true}).then(
207                 function(list) {
208                     egCore.env.absorbList(list, hint);
209                     service.authority_control_set[cache_key] = list;
210                 }
211             );
212         };
213
214         this._fetch = function(cmap) {
215             var deferred = $q.defer();
216             var promises = [];
217             for (var hint in cmap) {
218                 promises.push(this._fetch_class(hint, cmap[hint]));
219             }
220             $q.all(promises).then(function() {
221                 deferred.resolve();
222             });
223             return deferred.promise;
224         };
225
226         this._parse = function() {
227             service.authority_control_set._browse_axis_by_code = {};
228             service.authority_control_set._browse_axis_list.forEach(function (ba) {
229                 ba.maps(
230                     service.authority_control_set._browse_field_map_list.filter(
231                         function (m) { return m.axis() == ba.code() }
232                     )
233                 );
234                 service.authority_control_set._browse_axis_by_code[ba.code()] = ba;
235             });
236     
237             // loop over each acs
238             service.authority_control_set._control_set_list.forEach(function (cs) {
239                 service.authority_control_set._controlsets[''+cs.id()] = {
240                     id : cs.id(),
241                     name : cs.name(),
242                     description : cs.description(),
243                     authority_tag_map : {},
244                     control_map : {},
245                     bib_fields : [],
246                     raw : cs
247                 };
248     
249                 // grab the authority fields
250                 var acsaf_list = service.authority_control_set._authority_field_list.filter(
251                     function (af) { return af.control_set() == cs.id() }
252                 );
253     
254                 var at_list = service.authority_control_set._thesaurus_list.filter(
255                     function (at) { return at.control_set() == cs.id() }
256                 );
257     
258                 service.authority_control_set._controlsets[''+cs.id()].raw.authority_fields( acsaf_list );
259                 service.authority_control_set._controlsets[''+cs.id()].raw.thesauri( at_list );
260     
261                 // and loop over each
262                 acsaf_list.forEach(function (csaf) {
263                     csaf.axis_maps([]);
264     
265                     // link the main entry if we're subordinate
266                     if (csaf.main_entry()) {
267                         csaf.main_entry(
268                             acsaf_list.filter(function (x) {
269                                 return x.id() == csaf.main_entry();
270                             })[0]
271                         );
272                     }
273     
274                     // link the sub entries if we're main
275                     csaf.sub_entries(
276                         acsaf_list.filter(function (x) {
277                             return x.main_entry() == csaf.id();
278                         })
279                     );
280     
281                     // now, bib fields
282                     var acsbf_list = service.authority_control_set._bib_field_list.filter(
283                         function (b) { return b.authority_field() == csaf.id() }
284                     );
285                     csaf.bib_fields( acsbf_list );
286     
287                     service.authority_control_set._controlsets[''+cs.id()].bib_fields = [].concat(
288                         service.authority_control_set._controlsets[''+cs.id()].bib_fields,
289                         acsbf_list
290                     );
291     
292                     acsbf_list.forEach(function (csbf) {
293                         // link the authority field to the bib field
294                         if (csbf.authority_field()) {
295                             csbf.authority_field(
296                                 acsaf_list.filter(function (x) {
297                                     return x.id() == csbf.authority_field();
298                                 })[0]
299                             );
300                         }
301     
302                     });
303     
304                     service.authority_control_set._browse_axis_list.forEach(
305                         function (ba) {
306                             ba.maps().filter(
307                                 function (m) { return m.field() == csaf.id() }
308                             ).forEach(
309                                 function (fm) { fm.field( csaf ); csaf.axis_maps().push( fm ) } // and set the field
310                             )
311                         }
312                     );
313     
314                 });
315     
316                 // build the authority_tag_map
317                 service.authority_control_set._controlsets[''+cs.id()].bib_fields.forEach(function (bf) {
318     
319                     if (!service.authority_control_set._controlsets[''+cs.id()].control_map[bf.tag()])
320                         service.authority_control_set._controlsets[''+cs.id()].control_map[bf.tag()] = {};
321     
322                     bf.authority_field().sf_list().split('').forEach(function (sf_code) {
323     
324                         if (!service.authority_control_set._controlsets[''+cs.id()].control_map[bf.tag()][sf_code])
325                             service.authority_control_set._controlsets[''+cs.id()].control_map[bf.tag()][sf_code] = {};
326     
327                         service.authority_control_set._controlsets[''+cs.id()].control_map[bf.tag()][sf_code][bf.authority_field().tag()] = sf_code;
328                     });
329                 });
330     
331             });
332     
333             if (this.controlSetList().length > 0)
334                 delete service.authority_control_set._controlsets['-1'];
335     
336         }
337     
338         this.controlSetId = function (x) {
339             if (x) this._controlset = ''+x;
340             return this._controlset;
341         }
342
343         this.controlSetList = function () {
344             var l = [];
345             for (var i in service.authority_control_set._controlsets) {
346                 l.push(i);
347             }
348             return l;
349         }
350     
351     
352         if (!service.authority_control_set._remote_loaded) {
353     
354             // TODO -- push the raw tree into the oils cache for later reuse
355     
356             // fetch everything up front...
357             var parent = this;
358             this._fetch({
359                 "acs": "_control_set_list",
360                 "at": "_thesaurus_list",
361                 "acsaf": "_authority_field_list",
362                 "acsbf": "_bib_field_list",
363                 "aba": "_browse_axis_list",
364                 "abaafm": "_browse_field_map_list"
365             }).then(function() {
366                 service.authority_control_set._remote_loaded = true;
367                 parent._parse();
368                 if (kwargs.controlSet) {
369                     parent.controlSetId( kwargs.controlSet );
370                 } else {
371                     parent.controlSetId( parent.controlSetList().sort(function(a,b){return (a - b)}) );
372                 }
373             });
374         }
375
376         this.controlSet = function (x) {
377             return service.authority_control_set._controlsets[''+this.controlSetId(x)];
378         }
379     
380         this.controlSetByThesaurusCode = function (x) {
381             var thes = service.authority_control_set._thesaurus_list.filter(
382                 function (at) { return at.code() == x }
383             )[0];
384     
385             return this.controlSet(thes.control_set());
386         }
387     
388         this.browseAxisByCode = function(code) {
389             return service.authority_control_set._browse_axis_by_code[code];
390         }
391     
392         this.bibFieldByTag = function (x) {
393             var me = this;
394             return me.controlSet().bib_fields.filter(
395                 function (bf) { if (bf.tag() == x) return true }
396             )[0];
397         }
398     
399         this.bibFields = function (x) {
400             return this.controlSet(x).bib_fields;
401         }
402     
403         this.bibFieldBrowseAxes = function (t) {
404             var blist = [];
405             for (var bcode in service.authority_control_set._browse_axis_by_code) {
406                 service.authority_control_set._browse_axis_by_code[bcode].maps().forEach(
407                     function (m) {
408                         if (m.field().bib_fields().filter(
409                                 function (b) { return b.tag() == t }
410                             ).length > 0
411                         ) blist.push(bcode);
412                     }
413                 );
414             }
415             return blist;
416         }
417     
418         this.authorityFields = function (x) {
419             return this.controlSet(x).raw.authority_fields();
420         }
421     
422         this.thesauri = function (x) {
423             return this.controlSet(x).raw.thesauri();
424         }
425     
426         this.findControlSetsForTag = function (tag) {
427             var me = this;
428             var old_acs = this.controlSetId();
429             var acs_list = me.controlSetList().filter(
430                 function(acs_id) { return (me.controlSet(acs_id).control_map[tag]) }
431             );
432             this.controlSetId(old_acs);
433             return acs_list;
434         }
435     
436         this.findControlSetsForAuthorityTag = function (tag) {
437             var me = this;
438             var old_acs = this.controlSetId();
439     
440             var acs_list = me.controlSetList().filter(
441                 function(acs_id) {
442                     var a = me.controlSet(acs_id);
443                     for (var btag in a.control_map) {
444                         for (var sf in a.control_map[btag]) {
445                             if (a.control_map[btag][sf][tag]) return true;
446                         }
447                     }
448                     return false;
449                 }
450             );
451             this.controlSetId(old_acs);
452             return acs_list;
453         }
454     
455         this.bibToAuthority = function (field) {
456             var b_field = this.bibFieldByTag(field.tag);
457     
458             if (b_field) { // construct an marc authority record
459                 var af = b_field.authority_field();
460     
461                 var sflist = [];                
462                 for (var i = 0; i < field.subfields.length; i++) {
463                     if (af.sf_list().indexOf(field.subfields[i][0]) > -1) {
464                         if (typeof(field.subfields[i][1]) != 'undefined'
465                             && field.subfields[i][1] !== null
466                             && field.subfields[i][1].length > 0
467                         ) {
468                                 sflist.push(field.subfields[i]);
469                         }
470                     }
471                 }
472                 if (sflist.length == 0) return null;
473
474                 var m = new MARC21.Record ({rtype:'AUT'});
475                 m.appendFields(
476                     new MARC21.Field ({
477                         tag : af.tag(),
478                         ind1: field.ind1,
479                         ind2: field.ind2,
480                         subfields: sflist
481                     })
482                 );
483     
484                 return m.toXmlString();
485             }
486     
487             return null;
488         }
489     
490         this.bibToAuthorities = function (field) {
491             var auth_list = [];
492             var me = this;
493     
494             var old_acs = this.controlSetId();
495             me.controlSetList().forEach(
496                 function (acs_id) {
497                     var acs = me.controlSet(acs_id);
498                     var x = me.bibToAuthority(field);
499                     if (x) { var foo = {}; foo[acs_id] = x; auth_list.push(foo); }
500                 }
501             );
502             this.controlSetId(old_acs);
503     
504             return auth_list;
505         }
506     
507     }
508
509     service.getAuthorityControlSet = function() {
510         if (!service._active_control_set) {
511             service.authority_control_set._remote_loaded = false;
512             service._active_control_set = new service.authorityControlSet();
513         }
514         return service._active_control_set;
515     }
516
517     // fetches and caches the full set of values from 
518     // config.marc21_physical_characteristic_type_map
519     service.getPhysCharTypeMap = function() {
520
521         if (service.phys_char_type_map) {
522             return $q.when(service.phys_char_type_map);
523         }
524
525         return egCore.pcrud.retrieveAll('cmpctm', {}, {atomic : true})
526         .then(function(map) {return service.phys_char_type_map = map});
527     }
528
529     // Fetch+caches the config.marc21_physical_characteristic_subfield_map
530     // values for the requested ptype_key (i.e. type_map.ptype_key).
531     // Values are sorted by start_pos
532     service.getPhysCharSubfieldMap = function(ptype_key) {
533
534         if (service.phys_char_sf_map[ptype_key]) {
535             return $q.when(service.phys_char_sf_map[ptype_key]);
536         }
537
538         return egCore.pcrud.search('cmpcsm', 
539             {ptype_key : ptype_key},
540             {order_by : {cmpcsm : ['start_pos']}},
541             {atomic : true}
542         ).then(function(maps) {
543             return service.phys_char_sf_map[ptype_key] = maps;
544         });
545     }
546
547     // Fetches + caches the config.marc21_physical_characteristic_value_map
548     // for the requested ptype_subfield (subfield_map.id).  
549     // Maps are ordered by value.
550     service.getPhysCharValueMap = function(ptype_subfield) {
551         if (service.phys_char_value_map[ptype_subfield]) {
552             return $q.when(service.phys_char_value_map[ptype_subfield]);
553         }
554
555         return egCore.pcrud.search('cmpcvm', 
556             {ptype_subfield : ptype_subfield},
557             {order_by : {cmpcsm : ['value']}},
558             {atomic : true}
559         ).then(function(maps) {
560             return service.phys_char_sf_map[ptype_subfield] = maps;
561         });
562     }
563
564     return service;
565 }]);