]> git.evergreen-ils.org Git - Evergreen.git/blob - Open-ILS/web/js/ui/default/staff/serials/services/core.js
LP1615805 No inputs after submit in patron search (AngularJS)
[Evergreen.git] / Open-ILS / web / js / ui / default / staff / serials / services / core.js
1 angular.module('egSerialsMod', ['egCoreMod'])
2 .factory('egSerialsCoreSvc',
3        ['egCore','orderByFilter','$q','$filter','$uibModal','ngToast','egConfirmDialog',
4 function(egCore , orderByFilter , $q , $filter , $uibModal , ngToast , egConfirmDialog) {
5     var DAY = 86400000;
6     var service = {
7         bibId : null,
8         subId : null,
9         subTree : [],
10         subList : [],
11         sptList : [],
12         mfhdList : [],
13         potentialPatternList : [],
14         flatMfhdList : [],
15         itemMap : {},
16         itemTree : [],
17         itemList : [],
18         freq_offset : {
19             a : 365 * DAY,
20             b : 62 * DAY,
21             c : 4 * DAY,
22             d : DAY,
23             e : 14 * DAY,
24             f : 186 * DAY,
25             g : 2 * 365 * DAY,
26             h : 3 * 365 * DAY,
27             i : 2 * DAY,
28             j : 10 * DAY,
29             k : DAY,
30             m : 31 * DAY,
31             q : 93 * DAY,
32             s : 14 * DAY,
33             t : 124 * DAY,
34             w : 7 * DAY,
35             x : 0
36         },
37         freq_chrons : {
38             a : ['year'],
39             b : ['year','month'],
40             c : ['year','month'],
41             d : ['year','month','day'],
42             e : ['year','month','day'],
43             f : ['year','month'],
44             g : ['year'],
45             h : ['year','month'],
46             i : ['year','month','day'],
47             j : ['year','month','day'],
48             k : ['year','month','day'],
49             m : ['year','month'],
50             q : ['year','season'],
51             s : ['year','month'],
52             t : ['year','month','day'],
53             w : ['year','month','day'],
54             x : ['year','month','day']
55         },
56         get_chron_part : {
57             year  : function(d) { return d.getFullYear() },
58             season: function(d) { return _loose_season(d) },
59             month : function(d) { return ('00' + (d.getMonth() + 1)).slice(-2) },
60             week  : function(d) { return $filter('date')(d, 'ww') },
61             day   : function(d) { return ('00'+d.getDate()).slice(-2) },
62             hour  : function(d) { return ('00'+d.getHours()).slice(-2) }
63         },
64         item_status_list : [
65             'Expected',
66             'Received',
67             'Claimed',
68             'Bindery',
69             'Bound',
70             'Discarded',
71             'Not Held',
72             'Not Published'
73         ],
74         item_status_i18n : []
75     };
76
77     angular.forEach(service.item_status_list, function(status) {
78         service.item_status_i18n.push({
79             name  : status,
80             label : egCore.strings.SERIALS_ITEM_STATUS[status]
81         });
82     });
83
84     function _loose_season(D) {
85         var m = D.getMonth() + 1;
86         var d = D.getDate();
87
88         if (
89             (m == 1 || m == 2) || (m == 12 && d >= 21) || (m == 3 && d < 20)
90         ) {
91             return 24;  /* MFHD winter */
92         } else if (
93             (m == 4 || m == 5) || (m == 3 && d >= 20) || (m == 6 && d < 21)
94         ) {
95             return 21;  /* spring */
96         } else if (
97             (m == 7 || m == 8) || (m == 6 && d >= 21) || (m == 9 && d < 22)
98         ) {
99             return 22;  /* summer */
100         } else {
101             return 23;  /* autumn */
102         }
103     }
104
105     service.fetch_mfhds = function(bibId, contextOrg) {
106         // TODO filter by contextOrg
107         return egCore.pcrud.search('sre', {
108                 record       : bibId,
109                 deleted      : 'f',
110                 active       : 't'
111             }, {
112                 flesh : 3,
113                 flesh_fields : {
114                     'sre' : ['owning_lib']
115                 }
116             },
117             { atomic : true }
118         ).then(function(list) {
119             service.bibId = bibId;
120             service.mfhdList = list;
121             update_flat_mfhd_list();
122         });
123     }
124
125     service.fetch_patterns_from_bibs_mfhds = function(bibId) {
126         return egCore.net.request(
127             'open-ils.serial',
128             'open-ils.serial.caption_and_pattern.find_legacy_by_bib_record.atomic',
129             egCore.auth.token(),
130             bibId
131         ).then(function(list) {
132             service.potentialPatternList = egCore.idl.toTypedHash(list);
133             angular.forEach(service.potentialPatternList, function(pot) {
134                 var rec = new MARC21.Record({ marcxml : pot.marc });
135                 var pattern_fields = rec.fields.filter(function(f) {
136                     return (f.tag == '853' || f.tag == '854' || f.tag == '855');
137                 });
138                 pot.desc = '';
139                 if (pattern_fields.length > 0) {
140                     // just take the first one
141                     var fld = pattern_fields[0];
142                     pot.desc = fld.tag + ' ' + fld.ind1 + fld.ind2 +
143                                fld.subfields.map(function(sf) { 
144                                  return '$' + sf[0] + sf[1]
145                                }).join('');
146                 }
147             });
148         })
149     }
150
151     // fetch subscription, distributions, streams, captions,
152     // and notes associated with the indicated bib
153     service.fetch = function(bibId, contextOrg) {
154
155         var filter = { record_entry : bibId };
156         if (contextOrg) filter.owning_lib = egCore.org.descendants(contextOrg, true);
157         return egCore.pcrud.search('ssub', filter,
158             {
159                 flesh : 5,
160                 flesh_fields : {
161                     'ssub'  : ['owning_lib','distributions', 'scaps', 'notes'],
162                     'sdist' : [ 'record_entry','holding_lib',
163                                 'receive_call_number',
164                                 'receive_unit_template',
165                                 'bind_call_number',
166                                 'bind_unit_template',
167                                 'streams','notes'],
168                     'sstr'  : ['routing_list_users'],
169                     'srlu'  : ['reader'],
170                     'au'    : ['card','home_ou','mailing_address','billing_address']
171                 }
172             },
173             { atomic : true }
174         ).then(function(list) {
175             service.bibId = bibId;
176             service.subTree = list;
177             update_flat_sdist_sstr_list();
178             return $q.when(list);
179         });
180     }
181
182     // fetch subscription, distributions, streams, captions,
183     // and notes associated with the indicated bib
184     service.fetchLastCallnumber = function(contextOrg) {
185         return egCore.pcrud.search('acn', {
186                 record : service.bibId,
187                 owning_lib : contextOrg,
188                 deleted : 'f'
189             }, { flesh : 1,
190                  flesh_fields : {acn : ['prefix','suffix']},
191                  order_by : [{class:'acn',field:'create_date',direction:'desc'}],
192                  limit : 1
193             }, { atomic : true }
194         ).then(function(list) {
195             return $q.when(list[0]);
196         });
197     }
198
199     service.fetchItemsForSubPaged = function(subId,filter,offset,limit,sort) {
200         return service.fetchItemsForSub(
201             subId,
202             filter,
203             { limit : limit, offset : offset, paging : true },
204             sort
205         );
206     }
207
208     // Creates an inverted tree from item to sub
209     service.fetchItemsForSub = function(subId,filter,options,sort) {
210         var deferred = $q.defer(); // side-effects only, otherwise the grid is wonky
211
212         if (!filter) filter = {};
213         if (!options) options = { limit : 100 }; // only used during full refresh
214
215         if (!subId && service.subId) subId = service.subId;
216         if (!subId) return $q.reject('fetchItemsForSub: no subscription id');
217
218         var sub = service.get_ssub(subId);
219         if (!sub) return $q.reject('fetchItemsForSub: unknown subscription id');
220
221         var streams = [];
222         angular.forEach(sub.distributions(), function(dist) {
223             angular.forEach(
224                 dist.streams().map(
225                     function (stream) { return stream.id() }
226                 ),
227                 function (sid) { streams.push(sid) }
228             );
229         });
230
231         angular.extend(filter, {stream:streams});
232         angular.extend(options, { 
233             order_by : [{class:'sitem',field:'date_expected'}], // best aprox of pub date
234             flesh : 1,
235             flesh_fields : {
236                 sitem : ['notes','issuance','editor','creator','unit','url']
237             }
238         });
239         if (sort) {
240             angular.extend(options, {
241                 order_by : [sort]
242             });
243         }
244
245         egCore.pcrud.search(
246             'sitem', filter, options,
247             { atomic : true }
248         ).then(function(list) {
249             service.subId = subId;
250             if (!options.paging) { // not paged
251                 service.itemTree = list;
252                 service.itemMap = {};
253             } else { // paged
254                 angular.forEach(list, function (item) {
255                     var exists = service.itemTree.filter(function (i) {
256                         return i.id() == item.id()
257                     }).length;
258                     if (!exists) service.itemTree.push(item);
259                 });
260             }
261
262             // map items by stream for faster lookup
263             var tmp = {};
264             angular.forEach(list, function(item) {
265                 if (!tmp[item.stream()]) tmp[item.stream()] = [];
266                 tmp[item.stream()].push(item);
267                 service.itemMap[item.id()] = item;
268             });
269
270             angular.forEach(sub.distributions(), function(dist) {
271                 angular.forEach(dist.streams(), function(stream) {
272                     angular.forEach(tmp[stream.id()], function (item) {
273                         var routing_list = egCore.idl.Clone(stream.routing_list_users());
274                         var st = egCore.idl.Clone(stream,1);
275                         st.routing_list_users(routing_list);
276                         var d = egCore.idl.Clone(dist,1);
277                         var ss = egCore.idl.Clone(sub,1);
278                         ss.distributions([]);
279                         d.subscription(ss);
280                         d.streams([]);
281                         st.distribution(d);
282                         item.stream(st);
283                     });
284                 });
285             });
286
287             var hashList = egCore.idl.toHash(service.itemTree);
288             angular.forEach(hashList, function (item) {
289                 item['issuance.date_published'] = item.issuance.date_published;
290                 item['stream.distribution.holding_lib.name'] = item.stream.distribution.holding_lib.name;
291             });
292
293             // ... then sort it
294             if (sort) {
295                 service.itemList = hashList;
296             } else {
297                 service.itemList = orderByFilter(hashList, ['"issuance.date_published"', '"stream.distribution.holding_lib.name"', '"id"']);
298             }
299             deferred.resolve();
300         });
301
302         return deferred.promise;
303     }
304
305     service.prep_new_holding_code = function (args) {
306
307         var type = args.type;
308         var date = args.date;
309         var prev_iss = args.prev_iss;
310         var curr_iss = args.curr_iss;
311         var adhoc = false;
312         var link = '1.1';
313         var current_values = {};
314
315         var sub = service.get_ssub(service.subId);
316         if (!sub) return args;
317
318         var scap;
319         var pattern_changed = false;
320         if (prev_iss && prev_iss.holding_code()) { // we're predicting
321             var old_link_parts = JSON.parse(prev_iss.holding_code())[3].split('.');
322             var olink = old_link_parts[0];
323             var oseq = parseInt(old_link_parts[1]) + 1;
324             link = [olink,oseq].join('.');
325
326             if (prev_iss.holding_type())
327                 type = prev_iss.holding_type();
328
329             if (prev_iss.caption_and_pattern()) {
330                 var tmp = sub.scaps().filter(function (s) {
331                     return (s.id() == prev_iss.caption_and_pattern() && s.active() == 't');
332                 });
333                 if (angular.isArray(tmp) && tmp[0]) {
334                     scap = tmp[0];
335                 } else {
336                     // pattern associated with last issue must no longer be active
337                     pattern_changed = true;
338                 }
339             }
340
341             date = new Date(prev_iss.date_published());
342         } else if (curr_iss) { // we're editing
343             if (curr_iss.holding_type())
344                 type = curr_iss.holding_type();
345
346             if (curr_iss.caption_and_pattern()) {
347                 var tmp = sub.scaps().filter(function (s) {
348                     return (s.id() == curr_iss.caption_and_pattern());
349                 });
350                 if (angular.isArray(tmp) && tmp[0]) scap = tmp[0];
351             }
352             if (!curr_iss.holding_code()) {
353                 adhoc = true;
354             } else {
355                 var tmp = JSON.parse(curr_iss.holding_code());
356                 for (var i = 2; i < tmp.length; i += 2) {
357                     // we're intentionally being a bit sloppy here, as
358                     // the only subfields we are about in this context
359                     // are the ones that are not repeatable
360                     current_values[tmp[i]] = tmp[i + 1];
361                 }
362             }
363
364             date = new Date(curr_iss.date_published());
365         } else {
366             // starting from scratch, so default the
367             // first publication date to the subscription start date
368             if (!date) date = new Date(sub.start_date());
369         }
370
371         args.date = date;
372
373         if (!scap) {
374             var tmp = sub.scaps().filter(function (s) {
375                 return (s.type() == type && s.active() == 't');
376             });
377             if (angular.isArray(tmp) && tmp[0]) scap = tmp[0];
378         }
379
380         if (!scap) return args;
381
382         var others = [], enums = [], chrons = [], freq = '';
383         var pat = JSON.parse(scap.pattern_code()).slice(4); // just the part we care about
384
385         var freq_index = pat.indexOf('w');
386         if (freq_index > -1) {
387             freq = pat[freq_index + 1];
388             if (prev_iss && !args.pattern_changed) {
389                 date = new Date(
390                     date.getTime() + service.freq_offset[freq]
391                 );
392             }
393         }
394        
395         if (!date) date = new Date();
396
397         for (var i = 0; i < pat.length; i++) {
398             sf = pat[i]; i++;
399             val = pat[i];
400
401             if (sf != 'w') {
402                 var pat_part = {
403                     subfield : sf,
404                     pattern  : val
405                 };
406
407                 var chron_part = String(val).replace(/[)(]+/g,'');
408                 if (sf in current_values) {
409                     pat_part.value = current_values[sf];
410                 } else {
411                     try {
412                         pat_part.value = service.get_chron_part[chron_part](date);
413                     } catch (e) {
414                         // not a chron part
415                         pat_part.value = '';
416                     }
417                 }
418
419                 if (sf.match(/[a-f]/)) {
420                     enums.push(pat_part);
421                 } else if (sf.match(/[i-l]/)) {
422                     chrons.push(pat_part);
423                 } else {
424                     others.push(pat_part);
425                 }
426             }
427         }
428
429         if (enums.length == 0 && chrons.length == 0) {
430             var parts = service.freq_chrons[freq];
431             if (parts.length) {
432                 angular.forEach(parts, function(p, ind) {
433                     var sf = !ind ? 'i' : !--ind ? 'j' : 'k';
434                     chrons.push({
435                         subfield : sf,
436                         value    : service.get_chron_part.year(date)
437                     });
438                 });
439             } else { 
440                 chrons = [
441                     { subfield : 'i', value : service.get_chron_part.year(date)  },
442                     { subfield : 'j', value : service.get_chron_part.month(date) },
443                     { subfield : 'k', value : service.get_chron_part.day(date)  }
444                 ];
445             }
446         }
447
448         return {
449             holding_code : ["4","1","8",link],
450             scap         : scap.id(),
451             type         : type,
452             date         : date,
453             enums        : enums,
454             chrons       : chrons,
455             others       : others,
456             freq         : freq,
457             adhoc        : adhoc,
458             pattern_changed : pattern_changed
459         };
460     }
461
462     service.new_holding_code = function (options) {
463         if (options === undefined) options = {};
464         options.count = options.count || 1;
465         options.label = options.label || '';
466
467         return $uibModal.open({
468             templateUrl: './serials/t_holding_code_dialog',
469             //size: 'lg',
470             //windowClass: 'eg-wide-modal',
471             backdrop: 'static',
472             controller:
473                 ['$scope', '$uibModalInstance', function($scope, $uibModalInstance) {
474                 $scope.focusMe = true;
475                 $scope.title = options.title;
476                 $scope.request_count = options.request_count;
477                 $scope.count = options.count;
478                 $scope.label = options.label;
479                 $scope.save_label = options.save_label;
480                 $scope.pubdate = options.date;
481                 $scope.type = options.type || 'basic';
482                 $scope.args = { adhoc : false };
483                 if (options.adhoc) $scope.args.adhoc = true;
484                 $scope.can_change_adhoc = options.can_change_adhoc;
485
486                 function refresh (n,o) {
487                     if (n && o && n !== o) {
488                         $scope.args = service.prep_new_holding_code({
489                             type : $scope.type,
490                             date : $scope.pubdate,
491                             prev_iss : options.prev_iss,
492                             curr_iss : options.curr_iss,
493                         });
494                         if (!options.can_change_adhoc && options.adhoc) $scope.args.adhoc = true;
495
496                         if ($scope.args.type && $scope.type != $scope.args.type)
497                             $scope.type = $scope.args.type;
498                         if ($scope.args.date)
499                             $scope.pubdate = $scope.args.date;
500
501                         delete options.prev_iss; // only use this once
502                         delete options.curr_iss; // only use this once
503                     }
504                 }
505
506                 $scope.$watch('count',function (n) {options.count = n});
507                 $scope.$watch('label',function (n) {options.label = n});
508                 $scope.$watch('type',refresh);
509                 $scope.$watch('pubdate',refresh);
510
511                 $scope.ok = function(args) { $uibModalInstance.close(args) }
512                 $scope.cancel = function () { $uibModalInstance.dismiss() }
513
514                 refresh(1,2); // force data loading
515             }]
516         }).result.then(function (args) {
517             if (args.enums && args.chrons) {
518                 angular.forEach(
519                     args.enums.concat(args.chrons),
520                     function (e) {
521                         args.holding_code.push(e.subfield);
522                         args.holding_code.push(e.value);
523                     }
524                 );
525             }
526             args.count = options.count;
527             args.label = options.label;
528             return $q.when(args);
529         });
530     }
531
532     function update_flat_mfhd_list() {
533         var list = [];
534         angular.forEach(service.mfhdList, function(sre) {
535             var mfhdHash = egCore.idl.toHash(sre);
536             var rec = new MARC21.Record({ marcxml : mfhdHash.marc });
537             var _mfhd = {
538                 'id'                   : mfhdHash.id,
539                 'owning_lib.name'      : mfhdHash.owning_lib.name,
540                 'owning_lib.id'        : mfhdHash.owning_lib.id,
541                 'marc'                 : rec.toBreaker(),
542                 'marc_xml'             : mfhdHash.marc,
543                 'svr'                  : null,
544                 'basic_holdings'       : null,
545                 'index_holdings'       : null,
546                 'supplement_holdings'  : null
547             }
548             list.push(_mfhd);
549             egCore.net.request(
550                 'open-ils.search',
551                 'open-ils.search.serial.record.mfhd.retrieve',
552                 mfhdHash.id
553             ).then(function(svr) {
554                 _mfhd.svr = egCore.idl.toTypedHash(svr);
555                 _mfhd.basic_holdings = _mfhd.svr.basic_holdings.join("; ");
556                 _mfhd.index_holdings = _mfhd.svr.index_holdings.join("; ");
557                 _mfhd.supplement_holdings = _mfhd.svr.supplement_holdings.join("; ");
558             })
559         });
560         service.flatMfhdList.length = 0;
561         angular.extend(service.flatMfhdList, list);
562     }
563
564     // create/update a flat version of the subscription/distribution/stream
565     // tree for feeding to the distribution and stream grid
566     function update_flat_sdist_sstr_list() {
567
568         // flatten the structure...
569         var list = [];
570         angular.forEach(service.subTree, function(ssub) {
571             var ssubHash = egCore.idl.toHash(ssub);
572
573             var _ssub = {
574                 'id'                   : ssubHash.id,
575                 'owning_lib.name'      : ssubHash.owning_lib.name,
576                 'owning_lib.id'        : ssubHash.owning_lib.id,
577                 'start_date'           : ssubHash.start_date,
578                 'end_date'             : ssubHash.end_date,
579                 'expected_date_offset' : ssubHash.expected_date_offset
580             };
581             // insert and escape if we have no distributions
582             if (ssubHash.distributions.length == 0) {
583                 list.push(_ssub);
584                 return;
585             }
586
587             angular.forEach(ssubHash.distributions, function(sdist) {
588                 var _sdist = {};
589                 angular.forEach([
590                     'id',
591                     'summary_method',
592                     'record_entry',
593                     'label',
594                     'display_grouping',
595                     'unit_label_prefix',
596                     'unit_label_suffix',
597                 ], function(fld) {
598                     _sdist['sdist.' + fld] = sdist[fld];
599                 });
600                 _sdist['sdist.holding_lib.name'] = sdist.holding_lib.name;
601                 _sdist['sdist.holding_lib.id'] = sdist.holding_lib.id;
602                 _sdist['sdist.receive_call_number.label'] = 
603                     sdist.receive_call_number ? sdist.receive_call_number.label : null;
604                 _sdist['sdist.receive_unit_template.name'] =
605                     sdist.receive_unit_template ? sdist.receive_unit_template.name : null;
606                 _sdist['sdist.bind_call_number.label'] =
607                     sdist.bind_call_number ? sdist.bind_call_number.label : null;
608                 _sdist['sdist.bind_unit_template.name'] =
609                     sdist.bind_unit_template ? sdist.bind_unit_template.name : null;
610                 // if we have no streams, add to the list and escape
611                 if (sdist.streams.length == 0) {
612                     var row = {};
613                     angular.extend(row, _ssub, _sdist);
614                     list.push(row);
615                     return;
616                 }
617
618                 angular.forEach(sdist.streams, function(sstr) {
619                     var _sstr = {
620                         'sstr.id'                 : sstr.id,
621                         'sstr.routing_label'      : sstr.routing_label,
622                         'sstr.additional_routing' : ((sstr.routing_list_users.length > 0) ? true : false)
623                     };
624                     var row = {};
625                     angular.extend(row, _ssub, _sdist, _sstr);
626                     list.push(row);
627                 });
628             });
629         });
630
631         // ... then sort it
632         service.subList.length = 0;
633         angular.extend(service.subList,
634             orderByFilter(list, ['"owning_lib.name"', '"start_date"', '"end_date"',
635                                  '"holding_lib.name"', '"sdist.id"', '"sstr.id"'])
636         );
637
638         // ... then remove duplication of owning library, distribution library,
639         // and distribution labels
640         var sub_lib = null;
641         var dist_lib = null;
642         var dist_label = null;
643         var index = 0;
644         angular.forEach(service.subList, function(row) {
645             row['index'] = index++;
646             if (sub_lib == row['owning_lib.name']) {
647                 row['owning_lib.name'] = null;
648             } else {
649                 sub_lib = row['owning_lib.name'];
650                 dist_lib = row['sdist.holding_lib.name'];
651                 dist_label = row['sdist.label'];
652                 return;
653             }
654             if (dist_lib == row['sdist.holding_lib.name']) {
655                 row['sdist.holding_lib.name'] = null;
656             } else {
657                 dist_lib = row['sdist.holding_lib.name'];
658             }
659             if (dist_label == row['sdist.label']) {
660                 row['sdist.label'] = null;
661             } else {
662                 dist_label = row['sdist.label'];
663             }
664         });
665     }
666
667     // verify that a subscription ID and bib ID are actually
668     // associated with each other
669     service.verify_subscription_id = function(bibId, ssubId) {
670         var deferred = $q.defer();
671         egCore.pcrud.search('ssub', {
672                 record_entry : bibId,
673                 id           : ssubId
674         }, {}, { atomic : true, idlist : true }
675         ).then(function(list) {
676             if (list.length == 1) {
677                 deferred.resolve(true);
678             } else {
679                 deferred.resolve(false);
680             }
681         });
682         return deferred.promise;
683     }
684
685     service.get_ssub = function(ssubId) {
686         if (!ssubId) return;
687         for (var i = 0; i <= service.subTree.length; i++) {
688             if (service.subTree[i].id() == ssubId) {
689                 return service.subTree[i];
690             }
691         }
692     }
693
694     service.fetch_spt = function() {
695         return egCore.net.request(
696             'open-ils.serial',
697             'open-ils.serial.pattern_template.retrieve.at.atomic',
698             egCore.auth.token(),
699             egCore.auth.user().ws_ou()
700         ).then(function(list) {
701             service.sptList.length = 0;
702             angular.extend(service.sptList, list);
703         });
704     }
705
706     // return a hash keyed by the supplied OU IDs of
707     // of the list of copy templates owned by the full OU path
708     // of each one
709     service.fetch_templates = function(orgs) {
710         var deferred = $q.defer();
711         var _x = angular.isArray(orgs) ? orgs : [ orgs ];
712         var _orgs = _x.map(function(o) {
713             return egCore.org.get(o);
714         });
715         var _fps = {};
716         var _relevant_orgs = [];
717         angular.forEach(_orgs, function(o) {
718             var _fp = egCore.org.fullPath(o, true);
719             _fps[o.id()] = _fp;
720             angular.forEach(_fp, function (o2) {
721                 if (_relevant_orgs.indexOf(o2) === -1) {
722                      _relevant_orgs.push(o2);
723                 }
724             });
725         });
726         egCore.pcrud.search('act',
727             {owning_lib : _relevant_orgs},
728             {order_by : { act : 'name' }}, {atomic : true}
729         ).then(function(list) {
730             var _tmpls = {};
731             angular.forEach(_orgs, function(o) {
732                 _tmpls[o.id()] = list.filter(function(x) {
733                     return _fps[o.id()].indexOf(x.owning_lib()) > -1;
734                 }).map(function(x) {
735                     return egCore.idl.toTypedHash(x);
736                 });
737             });
738             deferred.resolve(_tmpls);
739         });
740         return deferred.promise;
741     };
742
743     service.print_routing_lists = function (bibId, items, check, force, print_rl) {
744         if (!check && !print_rl && !force) return $q.when();
745
746         return egCore.net.request(
747             'open-ils.search',
748             'open-ils.search.biblio.record.mods_slim.retrieve',
749             bibId
750         ).then(function(mvr) {
751
752             var by_issuance = {};
753             angular.forEach(items, function (i) {
754                 if (check && !i._print_routing_list) return;
755                 if (!by_issuance[i.issuance().id()])
756                     by_issuance[i.issuance().id()] = [];
757                 by_issuance[i.issuance().id()].push(i);
758             });
759
760             var issuance_matrix = [];
761             angular.forEach(by_issuance, function (list) {
762                 issuance_matrix.push(list);
763             });
764
765             var deferred = $q.defer();
766             var promise = deferred.promise;
767
768             angular.forEach(issuance_matrix, function(item_list, index) {
769
770                 promise = promise.then(function(){
771                     return $uibModal.open({
772                         templateUrl: './serials/t_print_routing_list',
773                         size: 'lg',
774                         windowClass: 'eg-wide-modal',
775                         backdrop: 'static',
776                         controller:
777                         ['$scope', '$uibModalInstance', function($scope, $uibModalInstance) {
778                             var all_users = [];
779                             var all_streams = [];
780
781                             angular.forEach(item_list, function(i){
782                                 all_streams.push(i.stream());
783                                 all_users = all_users.concat(i.stream().routing_list_users());
784                             });
785
786                             $scope.xulg = {
787                                 show_print_button: true,
788                                 routing_list_data: {
789                                     streams : all_streams,
790                                     mvr     : mvr,
791                                     issuance: item_list[0].issuance(),
792                                     users   : orderByFilter(all_users, 'pos')
793                                 }
794                             };
795
796                             $scope.url = '/eg/serial/print_routing_list_users?ses=' + egCore.auth.token();
797                             $scope.last = index == issuance_matrix.length - 1 ? true : false; 
798                             $scope.ok = function() { $uibModalInstance.close() }
799                         }]
800                     }).result;
801                 });
802
803             });
804
805             return deferred.resolve();
806         });
807
808     }
809
810     service.set_item_status = function(newStatus, bibId, list, callback) {
811         if (!callback) callback = function () { return $q.when() }
812         if (!list.length) return $q.reject();
813
814         return egConfirmDialog.open(
815             egCore.strings.CONFIRM_CHANGE_ITEMS.status,
816             egCore.strings.CONFIRM_CHANGE_ITEMS_MESSAGE.status,
817             {items : list.length}
818         ).result.then(function () {
819             var promises = [$q.when()];
820             angular.forEach(list, function(item) {
821                 item.status(newStatus);
822                 promises.push(
823                     egCore.net.request(
824                         'open-ils.serial',
825                         'open-ils.serial.item.update',
826                         egCore.auth.token(),
827                         item
828                     ).then(function(res) {
829                         return $q.when();
830                     })
831                 );
832             });
833             $q.all(promises).then(function() {
834                 callback();
835             });
836         });
837     }
838     
839     service.process_items = function (mode, bibId, list, do_barcode, bind, print_rl, callback) {
840         if (!callback) callback = function () { return $q.when() }
841         if (!list.length) return $q.reject();
842
843         // deal with locations and circ mods for *NEW* units
844         var copy_locations = {};
845         var circ_mods = {};
846
847         // deal with barcodes and call numbers for *NEW* units
848         var barcodes = {};
849         var call_numbers = {};
850         var call_numbers_by_siss_and_sdist = {};
851
852         var deferred = $q.defer();
853         var current_promise = deferred.promise;
854         var last_promise;
855
856         var sitem_alerts = [];
857         var sdist_alerts = [];
858         var ssub_alerts = list[0].stream().distribution().subscription().notes().filter(function(n){
859             return n.alert() == 't';
860         })
861
862         var dist_seen = {};
863         angular.forEach(list, function(i) {
864             sitem_alerts = sitem_alerts.concat(
865                 i.notes().filter(function(n){
866                     return n.alert() == 't';
867                 })
868             );
869             var sdist = '_'+i.stream().distribution().id();
870             if (!dist_seen[sdist]) {
871                 dist_seen[sdist] = 1;
872                 sdist_alerts = sdist_alerts.concat(
873                     i.stream().distribution().notes().filter(function(n){
874                         return n.alert() == 't';
875                     })
876                 );
877             }
878         });
879
880         if (do_barcode || bind) {
881
882             last_promise = current_promise.then(function(){ return $uibModal.open({
883                 templateUrl: './serials/t_batch_receive',
884                 size: 'lg',
885                 windowClass: 'eg-wide-modal',
886                 backdrop: 'static',
887                 controller:
888                 ['$scope', '$uibModalInstance', function($scope, $uibModalInstance) {
889
890                     $scope.print_routing_lists = print_rl;
891                     $scope.barcode_items = do_barcode;
892                     $scope.force_bind = bind;
893                     $scope.bind = bind;
894                     $scope.items = list;
895                     $scope.ssub_alerts = ssub_alerts;
896                     $scope.sdist_alerts = sdist_alerts;
897                     $scope.sitem_alerts = sitem_alerts;
898                     $scope.acn_list = [];
899                     $scope.acnp_labels = [];
900                     $scope.acns_labels = [];
901                     $scope.acpl_list = [];
902
903                     $scope.cannot_print = function (index) {
904                         return $scope.items[index].stream().routing_list_users().length == 0 || ($scope.bind && index > 0);
905                     }
906
907                     $scope.bind_or_none = function (index) {
908                         return !$scope.barcode_items || ($scope.bind && index > 0);
909                     }
910
911                     $scope.focus_next_barcode = function (index) {
912                         index++;
913                         $('#item_barcode_'+index).focus().select();
914                     }
915
916                     $scope.fullCNLabel = function (cn) {
917                         var label = [cn.prefix.label,cn.label,cn.suffix.label].join(' ');
918                         return label;
919                     }
920
921                     $scope.apply_template_overrides = function (e) {
922                         if ($scope.selected_call_number) {
923                             angular.forEach($scope.items, function (i) {
924                                 i._call_number = $scope.selected_call_number.label;
925                                 i._cn_prefix = $scope.selected_call_number.prefix.label;
926                                 i._cn_suffix = $scope.selected_call_number.suffix.label;
927                             });
928                         }
929                         if ($scope.selected_circ_mod) {
930                             angular.forEach($scope.items, function (i) {
931                                 i._circ_mod = $scope.selected_circ_mod;
932                             });
933                         }
934                         if ($scope.selected_copy_location) {
935                             angular.forEach($scope.items, function (i) {
936                                 i._copy_location = $scope.selected_copy_location;
937                             });
938                         }
939                     }
940
941                     $scope.ok = function(items) { $uibModalInstance.close(items) }
942                     $scope.cancel = function () { $uibModalInstance.dismiss() }
943
944                     var dist_libs = {};
945                     var pile_o_promises = [$q.when()];
946
947                     // let's gather what we need...
948                     angular.forEach(list, function (i, index) {
949                         var dlib = i.stream().distribution().holding_lib().id();
950                         dist_libs[dlib] = egCore.org.fullPath(dlib, true);
951                         if (i.unit()) {
952                             i._barcode = i.unit().barcode();
953                             pile_o_promises.push(
954                                 egCore.pcrud.retrieve(
955                                     'acn', i.unit().call_number(),
956                                     {flesh : 1, flesh_fields : {acn : ['prefix','suffix']}}
957                                 ).then(function(cn){
958                                     if (cn.deleted() == 'f') {
959                                         i._call_number = cn.label();
960                                         i._cn_prefix = cn.prefix().label();
961                                         i._cn_suffix = cn.suffix().label();
962                                     }
963                                 })
964                             );
965                         } else {
966                             if (i.stream().distribution()[mode + '_call_number']() && 
967                                 i.stream().distribution()[mode + '_call_number']().deleted() == 'f'
968                             ) {
969                                 i._call_number = i.stream().distribution()[mode + '_call_number']().label();
970                             } else {
971                                 pile_o_promises.push(
972                                     service.fetchLastCallnumber(
973                                         i.stream().distribution().holding_lib().id()
974                                     ).then(function(cn){
975                                         if (cn) {
976                                             i._call_number = cn.label();
977                                             i._cn_prefix = cn.prefix().label();
978                                             i._cn_suffix = cn.suffix().label();
979                                         }
980                                     })
981                                 );
982                             }
983                         }
984
985                         if (i.stream().distribution()[mode + '_unit_template']()) {
986                             i._copy_location = i.stream().distribution()[mode + '_unit_template']().location();
987                             i._circ_mod = i.stream().distribution()[mode + '_unit_template']().circ_modifier();
988                         }
989
990                         if ($scope.print_routing_lists && !$scope.cannot_print(index))
991                             i._print_routing_list = true;
992
993                         i._receive = true;
994                     });
995
996                     // build unique list of orgs from distribution.holding_lib fullPaths
997                     var dist_lib_list = [];
998                     angular.forEach(dist_libs, function (l) {
999                         dist_lib_list = dist_lib_list.concat(l);
1000                     });
1001                     dist_lib_list = dist_lib_list.filter(function(v,i,s){
1002                         return s.indexOf(v) == i;
1003                     });
1004
1005                     // Copy locations only come from the workstation location, same as XUL
1006                     pile_o_promises.push(egCore.pcrud.search(
1007                         'acpl',
1008                         {owning_lib : egCore.org.fullPath(egCore.auth.user().ws_ou(), true)},
1009                         {},{ atomic : true }
1010                     ).then(function (list) {
1011                         $scope.acpl_list = list.map(function(i){return egCore.idl.toHash(i)});
1012                         return $q.when();
1013                     }));
1014
1015                     // Call numbers, however, come from anywhere the distributions might live
1016                     pile_o_promises.push(egCore.pcrud.search(
1017                         'acn',
1018                         {deleted : 'f', record : bibId, owning_lib : dist_lib_list},
1019                         {flesh : 1, flesh_fields : {acn : ['prefix','suffix']}},{ atomic : true }
1020                     ).then(function (list) {
1021                         $scope.acn_list = list.map(function(i){return egCore.idl.toHash(i)});
1022                         return $q.when();
1023                     }));
1024
1025                     // Likewise for prefix and suffix, for combo box
1026                     angular.forEach(['acnp','acns'], function (cl) {
1027                         pile_o_promises.push(egCore.pcrud.search(
1028                             cl,
1029                             {owning_lib : dist_lib_list},
1030                             {},{ atomic : true }
1031                         ).then(function (list) {
1032                             $scope[cl+'_labels'] = list.map(function(i){return i.label()});
1033                             return $q.when();
1034                         }));
1035                     });
1036
1037                     pile_o_promises.push(egCore.pcrud.retrieveAll(
1038                         'ccm', {}, { atomic : true }
1039                     ).then(function (list) {
1040                         $scope.ccm_list = list.map(function(i){return egCore.idl.toHash(i)});
1041                         return $q.when();
1042                     }));
1043
1044                     $q.all(pile_o_promises).then(function() {
1045                         console.log('receive data collected');
1046                     });
1047
1048                     $scope.$watch('barcode_items', function (n,o) {
1049                         if (n === undefined || n == o) return;
1050                         do_barcode = n;
1051                     });
1052
1053                     $scope.$watch('bind', function (n,o) {
1054                         if (n === undefined || n == o) return;
1055                         bind = n;
1056                         if (bind) {
1057                             angular.forEach($scope.items, function (i,index) {
1058                                 if (index > 0) i._print_routing_list = false;
1059                             });
1060                         }
1061                     });
1062                         
1063                     $scope.$watch('auto_barcodes', function (n,o) {
1064                         if (n === undefined || n == o) return;
1065
1066                         var bc = '@@AUTO';
1067                         if (!n) bc = '';
1068
1069                         angular.forEach($scope.items, function (i) {
1070                             if (!i.stream().distribution().receive_unit_template()) return;
1071                             var _barcode = i._barcode;
1072                             i._barcode = bc || i._old_barcode;
1073                             i._old_barcode = _barcode;
1074                         });
1075                     });
1076
1077                     $scope.$watch('print_routing_lists', function (n,o) {
1078                         if (n === undefined || n == o) return;
1079
1080                         angular.forEach($scope.items, function(i, index) {
1081                             if (!$scope.cannot_print(index)) {
1082                                 i._print_routing_list = n;
1083                             } else {
1084                                 i._print_routing_list = false;
1085                             }
1086                         });
1087                     });
1088                 }]
1089             }).result});
1090         } else {
1091             last_promise = current_promise.then(function(){ return $uibModal.open({
1092                 templateUrl: './serials/t_receive_alerts',
1093                 backdrop: 'static',
1094                 controller:
1095                 ['$scope', '$uibModalInstance', function($scope, $uibModalInstance) {
1096                     $scope.title = egCore.strings.CONFIRM_CHANGE_ITEMS[mode];
1097                     $scope.items = list.length;
1098                     $scope.list = list;
1099                     $scope.mode = mode;
1100                     $scope.ssub_alerts = ssub_alerts;
1101                     $scope.sdist_alerts = sdist_alerts;
1102                     $scope.sitem_alerts = sitem_alerts;
1103
1104                     $scope.ok = function(items) { $uibModalInstance.close(items) }
1105                     $scope.cancel = function () { $uibModalInstance.dismiss() }
1106                 }]
1107             }).result.then(
1108                 function(items) {
1109                     angular.forEach(list, function (i, index) {
1110                         i._receive = true;
1111                     });
1112                     return $q.when(list);
1113                 })
1114             });
1115         }
1116
1117         last_promise.then(function (items) {
1118
1119             var method;
1120             if (mode == 'receive') {
1121                 method = 'open-ils.serial.receive_items';
1122                 items = items.filter(function(i){return i._receive});
1123             } else if ( mode == 'bind') {
1124                 method = 'open-ils.serial.bind_items';
1125                 items = items.filter(function(i){return i._receive});
1126             } else if ( mode == 'reset') {
1127                 method = 'open-ils.serial.reset_items';
1128             } 
1129
1130             if (!items.length) return $q.reject();
1131
1132             var donor_unit_ids = {};
1133             angular.forEach(items, function(i, index) {
1134                 if (i.unit()) donor_unit_ids[i.unit().id()] = 1;
1135                 if (do_barcode) i.unit(-1);
1136                 if (bind) i.unit(-2);
1137                 copy_locations[i.id()] = i._copy_location;
1138                 circ_mods[i.id()] = i._circ_mod;
1139                 call_numbers[i.id()] = [i._cn_prefix, i._call_number, i._cn_suffix] || 'DEFAULT';
1140                 barcodes[i.id()] = i._barcode || '@@AUTO';
1141                 if (bind && index > 0) barcodes[i.id()] = items[0]._barcode;
1142             });
1143
1144             return egCore.net.request(
1145                 'open-ils.serial', method,
1146                 egCore.auth.token(), items, barcodes, call_numbers, donor_unit_ids,
1147                     {circ_mods:circ_mods, copy_locations : copy_locations}
1148             ).then(
1149                 function(resp) {
1150                     var evt = egCore.evt.parse(resp);
1151                     if (evt) {
1152                         ngToast.danger(egCore.strings.SERIALS_ISSUANCE_FAIL_SAVE);
1153                     } else {
1154                         ngToast.success(egCore.strings.SERIALS_ISSUANCE_SUCCESS_SAVE);
1155                         return service.print_routing_lists(bibId, items, do_barcode || bind, false, print_rl)
1156                             .finally(callback);
1157                     }
1158                 },
1159                 function(resp) {
1160                     ngToast.danger(egCore.strings.SERIALS_ISSUANCE_FAIL_SAVE);
1161                 }
1162             );
1163         });
1164
1165         return deferred.resolve();
1166     }
1167
1168     service.add_issuances = function (mySsubId) {
1169         if (!mySsubId && service.subId) mySsubId = service.subId;
1170         if (!mySsubId) return $q.reject('fetchItemsForSub: no subscription id');
1171
1172         var sub = service.get_ssub(mySsubId);
1173         if (!sub) return $q.reject('fetchItemsForSub: unknown subscription id');
1174
1175         var streams = [];
1176         angular.forEach(sub.distributions(), function(dist) {
1177             angular.forEach(
1178                 dist.streams().map(
1179                     function (stream) { return stream.id() }
1180                 ),
1181                 function (sid) { streams.push(sid) }
1182             );
1183         });
1184
1185         var options = { 
1186             order_by : [{class:'sitem',field:'date_expected',direction:'desc'}], // best aprox of pub date
1187             limit : 1,
1188             flesh : 1,
1189             flesh_fields : { sitem : ['issuance'] }
1190         };
1191
1192         return egCore.pcrud.search(
1193             'sitem', {stream:streams},
1194             {   order_by : [{class:'sitem',field:'date_expected',direction:'desc'}], // best aprox of pub date
1195                 limit : 1,
1196                 flesh : 1,
1197                 flesh_fields : { sitem : ['issuance'] }
1198             },
1199             { atomic : true }
1200         ).then(function(list) {
1201             var lastItem = list[0];
1202     
1203             if (lastItem) lastItem = lastItem.issuance();
1204     
1205             return service.new_holding_code({
1206                 title : egCore.strings.SERIALS_ISSUANCE_PREDICT,
1207                 request_count : true,
1208                 prev_iss : lastItem,
1209                 allow_adhoc : false
1210             }).then(function(hc) {
1211     
1212                 var base_iss;
1213                 var include_base_iss = 0;
1214                 if (!lastItem || hc.pattern_changed) {
1215                     include_base_iss = 1;
1216                     base_iss = new egCore.idl.siss();
1217                     base_iss.creator( egCore.auth.user().id() );
1218                     base_iss.editor( egCore.auth.user().id() );
1219                     base_iss.date_published( hc.date.toISOString() );
1220                     base_iss.subscription( mySsubId );
1221                     base_iss.caption_and_pattern( hc.scap );
1222                     base_iss.holding_code( JSON.stringify(hc.holding_code) );
1223                     base_iss.holding_type( hc.type );
1224                 }
1225
1226                 // if we're predicting without a preexisting holding, reduce the count
1227                 if (!lastItem) hc.count--;
1228     
1229                 return egCore.net.request(
1230                     'open-ils.serial',
1231                     'open-ils.serial.make_predictions',
1232                     egCore.auth.token(),
1233                     { ssub_id : mySsubId,
1234                       include_base_issuance : include_base_iss,
1235                       num_to_predict : hc.count,
1236                       base_issuance : base_iss || lastItem
1237                     }
1238                 ).then(
1239                     function(resp) {
1240                         var evt = egCore.evt.parse(resp);
1241                         if (evt) {
1242                             ngToast.danger(egCore.strings.SERIALS_ISSUANCE_FAIL_SAVE);
1243                         } else {
1244                             ngToast.success(egCore.strings.SERIALS_ISSUANCE_SUCCESS_SAVE);
1245                         }
1246                     },
1247                     function(resp) {
1248                         ngToast.danger(egCore.strings.SERIALS_ISSUANCE_FAIL_SAVE);
1249                     }
1250                 );
1251             });
1252         });
1253     }
1254
1255     return service;
1256 }]);
1257