LP1691269: Webstaff Copy Editor Templates
[Evergreen.git] / Open-ILS / web / js / ui / default / staff / cat / volcopy / app.js
1 /**
2  * Vol/Copy Editor
3  */
4
5 angular.module('egVolCopy',
6     ['ngRoute', 'ui.bootstrap', 'egCoreMod', 'egUiMod', 'egGridMod'])
7
8 .filter('boolText', function(){
9     return function (v) {
10         return v == 't';
11     }
12 })
13
14 .config(['ngToastProvider', function(ngToastProvider) {
15   ngToastProvider.configure({
16     verticalPosition: 'bottom',
17     animation: 'fade'
18   });
19 }])
20
21 .config(function($routeProvider, $locationProvider, $compileProvider) {
22     $locationProvider.html5Mode(true);
23     $compileProvider.aHrefSanitizationWhitelist(/^\s*(https?|mailto|blob):/); // grid export
24         
25     var resolver = {
26         delay : ['egStartup', function(egStartup) { return egStartup.go(); }]
27     };
28
29     $routeProvider.when('/cat/volcopy/edit_templates', {
30         templateUrl: './cat/volcopy/t_view',
31         controller: 'EditCtrl',
32         resolve : resolver
33     });
34
35     $routeProvider.when('/cat/volcopy/:dataKey', {
36         templateUrl: './cat/volcopy/t_view',
37         controller: 'EditCtrl',
38         resolve : resolver
39     });
40
41     $routeProvider.when('/cat/volcopy/:dataKey/:mode', {
42         templateUrl: './cat/volcopy/t_view',
43         controller: 'EditCtrl',
44         resolve : resolver
45     });
46 })
47
48 .factory('itemSvc', 
49        ['egCore','$q',
50 function(egCore , $q) {
51
52     var service = {
53         currently_generating : false,
54         auto_gen_barcode : false,
55         barcode_checkdigit : false,
56         new_cp_id : 0,
57         new_cn_id : 0,
58         tree : {}, // holds lib->cn->copy hash stack
59         copies : [] // raw copy list
60     };
61
62     service.nextBarcode = function(bc) {
63         service.currently_generating = true;
64         return egCore.net.request(
65             'open-ils.cat',
66             'open-ils.cat.item.barcode.autogen',
67             egCore.auth.token(),
68             bc, 1, { checkdigit: service.barcode_checkdigit }
69         ).then(function(resp) { // get_barcodes
70             var evt = egCore.evt.parse(resp);
71             if (!evt) return resp[0];
72             return '';
73         });
74     };
75
76     service.checkBarcode = function(bc) {
77         if (!service.barcode_checkdigit) return true;
78         if (bc != Number(bc)) return false;
79         bc = bc.toString();
80         // "16.00" == Number("16.00"), but the . is bad.
81         // Throw out any barcode that isn't just digits
82         if (bc.search(/\D/) != -1) return false;
83         var last_digit = bc.substr(bc.length-1);
84         var stripped_barcode = bc.substr(0,bc.length-1);
85         return service.barcodeCheckdigit(stripped_barcode).toString() == last_digit;
86     };
87
88     service.barcodeCheckdigit = function(bc) {
89         var reverse_barcode = bc.toString().split('').reverse();
90         var check_sum = 0; var multiplier = 2;
91         for (var i = 0; i < reverse_barcode.length; i++) {
92             var digit = reverse_barcode[i];
93             var product = digit * multiplier; product = product.toString();
94             var temp_sum = 0;
95             for (var j = 0; j < product.length; j++) {
96                 temp_sum += Number( product[j] );
97             }
98             check_sum += Number( temp_sum );
99             multiplier = ( multiplier == 2 ? 1 : 2 );
100         }
101         check_sum = check_sum.toString();
102         var next_multiple_of_10 = (check_sum.match(/(\d*)\d$/)[1] * 10) + 10;
103         var check_digit = next_multiple_of_10 - Number(check_sum); if (check_digit == 10) check_digit = 0;
104         return check_digit;
105     };
106
107     // returns a promise resolved with the list of circ mods
108     service.get_classifications = function() {
109         if (egCore.env.acnc)
110             return $q.when(egCore.env.acnc.list);
111
112         return egCore.pcrud.retrieveAll('acnc', null, {atomic : true})
113         .then(function(list) {
114             egCore.env.absorbList(list, 'acnc');
115             return list;
116         });
117     };
118
119     service.get_prefixes = function(org) {
120         return egCore.pcrud.search('acnp',
121             {owning_lib : egCore.org.fullPath(org, true)},
122             {order_by : { acnp : 'label_sortkey' }}, {atomic : true}
123         );
124
125     };
126
127     service.get_statcats = function(orgs) {
128         return egCore.pcrud.search('asc',
129             {owner : orgs},
130             { flesh : 1,
131               flesh_fields : {
132                 asc : ['owner','entries']
133               }
134             },
135             { atomic : true }
136         );
137     };
138
139     service.get_locations = function(orgs) {
140         return egCore.pcrud.search('acpl',
141             {owning_lib : orgs, deleted : 'f'},
142             {
143                 flesh : 1,
144                 flesh_fields : {
145                     acpl : ['owning_lib']
146                 },
147                 order_by : { acpl : 'name' }
148             },
149             {atomic : true}
150         );
151     };
152
153     service.get_suffixes = function(org) {
154         return egCore.pcrud.search('acns',
155             {owning_lib : egCore.org.fullPath(org, true)},
156             {order_by : { acns : 'label_sortkey' }}, {atomic : true}
157         );
158
159     };
160
161     service.get_magic_statuses = function() {
162         /* TODO: make these more configurable per lp1616170 */
163         return $q.when([
164              1  /* Checked out */
165             ,3  /* Lost */
166             ,6  /* In transit */
167             ,8  /* On holds shelf */
168             ,16 /* Long overdue */
169             ,18 /* Canceled Transit */
170         ]);
171     }
172
173     service.get_statuses = function() {
174         if (egCore.env.ccs)
175             return $q.when(egCore.env.ccs.list);
176
177         return egCore.pcrud.retrieveAll('ccs', {order_by : { ccs : 'name' }}, {atomic : true}).then(
178             function(list) {
179                 egCore.env.absorbList(list, 'ccs');
180                 return list;
181             }
182         );
183
184     };
185
186     service.get_circ_mods = function() {
187         if (egCore.env.ccm)
188             return $q.when(egCore.env.ccm.list);
189
190         return egCore.pcrud.retrieveAll('ccm', {}, {atomic : true}).then(
191             function(list) {
192                 egCore.env.absorbList(list, 'ccm');
193                 return list;
194             }
195         );
196
197     };
198
199     service.get_circ_types = function() {
200         if (egCore.env.citm)
201             return $q.when(egCore.env.citm.list);
202
203         return egCore.pcrud.retrieveAll('citm', {}, {atomic : true}).then(
204             function(list) {
205                 egCore.env.absorbList(list, 'citm');
206                 return list;
207             }
208         );
209
210     };
211
212     service.get_age_protects = function() {
213         if (egCore.env.crahp)
214             return $q.when(egCore.env.crahp.list);
215
216         return egCore.pcrud.retrieveAll('crahp', {}, {atomic : true}).then(
217             function(list) {
218                 egCore.env.absorbList(list, 'crahp');
219                 return list;
220             }
221         );
222
223     };
224
225     service.get_floating_groups = function() {
226         if (egCore.env.cfg)
227             return $q.when(egCore.env.cfg.list);
228
229         return egCore.pcrud.retrieveAll('cfg', {}, {atomic : true}).then(
230             function(list) {
231                 egCore.env.absorbList(list, 'cfg');
232                 return list;
233             }
234         );
235
236     };
237
238     service.bmp_parts = {};
239     service.get_parts = function(rec) {
240         if (service.bmp_parts[rec])
241             return $q.when(service.bmp_parts[rec]);
242
243         return egCore.pcrud.search('bmp',
244             {record : rec, deleted : 'f'},
245             null, {atomic : true}
246         ).then(function(list) {
247             service.bmp_parts[rec] = list;
248             return list;
249         });
250
251     };
252
253    service.get_acp_templates = function() {
254        // Already downloaded for this user? Return local copy. Changing users or logging out causes another download
255        // so users always have their own templates, and any changes made on other machines appear as expected.
256        if (egCore.hatch.getSessionItem('cat.copy.templates.usr') == egCore.auth.user().id()) {
257            return egCore.hatch.getItem('cat.copy.templates').then(function(templ) {
258                return templ;
259            });
260        } else {
261           // this can be disabled for debugging to force a re-download and translation of test templates
262           egCore.hatch.setSessionItem('cat.copy.templates.usr', egCore.auth.user().id());
263           return service.load_remote_acp_templates();
264       }
265
266    };
267
268    service.save_acp_templates = function(t) {
269        egCore.hatch.setItem('cat.copy.templates', t);
270        egCore.net.request('open-ils.actor', 'open-ils.actor.patron.settings.update',
271            egCore.auth.token(), egCore.auth.user().id(), { "webstaff.cat.copy.templates": t });
272        // console.warn('Saved ' + JSON.stringify({"webstaff.cat.copy.templates": t}));
273    };
274
275    service.load_remote_acp_templates = function() {
276        // After the XUL Client is completely removed everything related to staff_client.copy_editor.templates and convert_xul_templates can be thrown away.
277        return egCore.net.request('open-ils.actor', 'open-ils.actor.patron.settings.retrieve.authoritative',
278            egCore.auth.token(), egCore.auth.user().id(),
279            ['webstaff.cat.copy.templates','staff_client.copy_editor.templates']).then(function(settings) {
280                if (settings['webstaff.cat.copy.templates']) {
281                    egCore.hatch.setItem('cat.copy.templates', settings['webstaff.cat.copy.templates']);
282                    return settings['webstaff.cat.copy.templates'];
283                } else {
284                    if (settings['staff_client.copy_editor.templates']) {
285                       var new_templ = service.convert_xul_templates(settings['staff_client.copy_editor.templates']);
286                       egCore.hatch.setItem('cat.copy.templates', new_templ);
287                       // console.warn('Saving: ' + JSON.stringify({'webstaff.cat.copy.templates' : new_templ}));
288                       egCore.net.request('open-ils.actor', 'open-ils.actor.patron.settings.update',
289                           egCore.auth.token(), egCore.auth.user().id(), {'webstaff.cat.copy.templates' : new_templ});
290                       return new_templ;
291                    }
292                }
293                return {};
294        });
295    };
296
297    service.convert_xul_templates = function(xultempl) {
298        var conv_templ = {};
299        var templ_names = Object.keys(xultempl);
300        var name;
301        var xul_t;
302        var curr_templ;
303        var stat_cats;
304        var fields;
305        var field_name;
306        var curr_field;
307        var tmp_val;
308        var i, j;
309
310        if (templ_names){
311          for (i=0; i < templ_names.length; i++) {
312            name = templ_names[i];
313            curr_templ = {};
314            stat_cats = {};
315            xul_t  = xultempl[name];
316            fields = Object.keys(xul_t);
317
318            if (fields.length > 0) {
319              for (j=0; j < fields.length; j++) {
320                field_name = fields[j];
321                curr_field = xul_t[field_name];
322
323                if ( curr_field["field"] == null ) { continue; }
324                if ( curr_field["value"] == "<HACK:KLUDGE:NULL>" ) { continue; }
325
326                // floating changed from a boolean to an integer at one point; take this opportunity to remove the boolean from any old templates
327                if ( curr_field["type"] === "attribute" && curr_field["field"] === "floating" ) {
328                  if ( curr_field["value"].match(/[tf]/) ) { continue; }
329                }
330
331                if ( curr_field["type"] === "stat_cat" ) {
332                  stat_cats[curr_field["field"]] = parseInt(curr_field["value"]);
333                }
334                else {
335                  tmp_val = curr_field['value']; // so... some of the number fields are actually strings. Groovy.
336                  if ( tmp_val.match(/^[-0-9.]+$/) && !(curr_field["field"].match(/(?:loan_duration|fine_level)/))) { tmp_val = parseFloat(tmp_val); }
337                  curr_templ[curr_field["field"]] = tmp_val;
338                }
339              }
340
341              if ( (Object.keys(stat_cats)).length > 0 ){
342                curr_templ["statcats"] = stat_cats;
343              }
344
345              conv_templ[name] = curr_templ;
346            }
347          }
348        }
349        return conv_templ;
350    };
351
352     service.flesh = {   
353         flesh : 3, 
354         flesh_fields : {
355             acp : ['call_number','parts','stat_cat_entries', 'notes', 'tags'],
356             acn : ['label_class','prefix','suffix'],
357             acptcm : ['tag']
358         }
359     }
360
361     service.addCopy = function (cp) {
362
363         if (!cp.parts()) cp.parts([]); // just in case...
364
365         var lib = cp.call_number().owning_lib();
366         var cn = cp.call_number().id();
367
368         if (!service.tree[lib]) service.tree[lib] = {};
369         if (!service.tree[lib][cn]) service.tree[lib][cn] = [];
370
371         service.tree[lib][cn].push(cp);
372         service.copies.push(cp);
373     }
374
375     service.checkDuplicateBarcode = function(bc, id) {
376         var final = false;
377         return egCore.pcrud.search('acp', { deleted : 'f', 'barcode' : bc, id : { '!=' : id } })
378             .then(
379                 function () { return final },
380                 function () { return final },
381                 function () { final = true; }
382             );
383     }
384
385     service.fetchIds = function(idList) {
386         service.tree = {}; // clear the tree on fetch
387         service.copies = []; // clear the copy list on fetch
388         return egCore.pcrud.search('acp', { 'id' : idList }, service.flesh).then(null,null,
389             function(copy) {
390                 service.addCopy(copy);
391             }
392         );
393     }
394
395     // create a new acp object with default values
396     // (both hard-coded and coming from OU settings)
397     service.generateNewCopy = function(callNumber, owningLib, isFastAdd, isNew) {
398         var cp = new egCore.idl.acp();
399         cp.id( --service.new_cp_id );
400         if (isNew) {
401             cp.isnew( true );
402         }
403         cp.circ_lib( owningLib );
404         cp.call_number( callNumber );
405         cp.deposit(0);
406         cp.price(0);
407         cp.deposit_amount(0);
408         cp.fine_level(2); // Normal
409         cp.loan_duration(2); // Normal
410         cp.location(1); // Stacks
411         cp.circulate('t');
412         cp.holdable('t');
413         cp.opac_visible('t');
414         cp.ref('f');
415         cp.mint_condition('t');
416         cp.empty_barcode = true;
417
418         var status_setting = isFastAdd ?
419             'cat.default_copy_status_fast' :
420             'cat.default_copy_status_normal';
421         egCore.org.settings(
422             [status_setting],
423             owningLib
424         ).then(function(set) {
425             var default_ccs = parseInt(set[status_setting]);
426             if (isNaN(default_ccs))
427                 default_ccs = (isFastAdd ? 0 : 5); // 0 is Available, 5 is In Process
428             cp.status(default_ccs);
429         });
430
431         return cp;
432     }
433
434     return service;
435 }])
436
437 .directive('stringToNumber', function() {
438     return {
439         require: 'ngModel',
440         link: function(scope, element, attrs, ngModel) {
441             var precision = attrs.precision || 0;
442             ngModel.$parsers.push(function(value) {
443                 return value;
444             });
445
446             function updateDisplay() {
447                 var value = parseFloat(ngModel.$viewValue);
448                 if ( isNaN(value) ) { return; }
449                     element.val(value.toFixed(precision));
450             }
451
452             ngModel.$formatters.push(function(value) {
453                 return parseFloat(value);
454             });
455
456             ngModel.$viewChangeListeners.push(updateDisplay);
457
458             ngModel.$render = function() {
459                 updateDisplay();
460             }
461         }
462     };
463 })
464
465 .directive("egVolCopyEdit", function () {
466     return {
467         restrict: 'E',
468         replace: true,
469         template:
470             '<div class="row">'+
471                 '<div class="col-xs-5" ng-class="{'+"'has-error'"+':barcode_has_error}">'+
472                     '<input id="{{callNumber.id()}}_{{copy.id()}}"'+
473                     ' eg-enter="nextBarcode(copy.id())" class="form-control"'+
474                     ' type="text" ng-model="barcode" ng-change="updateBarcode()"/>'+
475                     '<div class="label label-danger" ng-if="duplicate_barcode">{{duplicate_barcode_string}}</div>'+
476                     '<div class="label label-danger" ng-if="empty_barcode">{{empty_barcode_string}}</div>'+
477                 '</div>'+
478                 '<div class="col-xs-3"><input class="form-control" type="number" min="1" ng-model="copy_number" ng-change="updateCopyNo()"/></div>'+
479                 '<div class="col-xs-4"><eg-basic-combo-box eg-disabled="record == 0" list="parts" selected="part"></eg-basic-combo-box></div>'+
480             '</div>',
481
482         scope: { focusNext: "=", copy: "=", callNumber: "=", index: "@", record: "@" },
483         controller : ['$scope','itemSvc','egCore',
484             function ( $scope , itemSvc , egCore ) {
485                 $scope.new_part_id = 0;
486                 $scope.barcode_has_error = false;
487                 $scope.duplicate_barcode = false;
488                 $scope.empty_barcode = false;
489                 $scope.duplicate_barcode_string = window.duplicate_barcode_string;
490                 $scope.empty_barcode_string = window.empty_barcode_string;
491
492                 if (!$scope.copy.barcode()) $scope.copy.empty_barcode = true;
493
494                 $scope.nextBarcode = function (i) {
495                     $scope.focusNext(i, $scope.barcode);
496                 }
497
498                 $scope.updateBarcode = function () {
499                     if ($scope.barcode != '') {
500                         $scope.copy.empty_barcode = $scope.empty_barcode = false;
501                         $scope.barcode_has_error = !Boolean(itemSvc.checkBarcode($scope.barcode));
502                         itemSvc.checkDuplicateBarcode($scope.barcode, $scope.copy.id())
503                             .then(function (state) { $scope.copy.duplicate_barcode = $scope.duplicate_barcode = state });
504                     } else {
505                         $scope.copy.empty_barcode = $scope.empty_barcode = true;
506                     }
507                         
508                     $scope.copy.barcode($scope.barcode);
509                     $scope.copy.ischanged(1);
510                     if (itemSvc.currently_generating)
511                         $scope.focusNext($scope.copy.id(), $scope.barcode);
512                 };
513
514                 $scope.updateCopyNo = function () { $scope.copy.copy_number($scope.copy_number); $scope.copy.ischanged(1); };
515                 $scope.updatePart = function () {
516                     if ($scope.part) {
517                         var p = $scope.part_list.filter(function (x) {
518                             return x.label() == $scope.part
519                         });
520                         if (p.length > 0) { // preexisting part
521                             $scope.copy.parts(p)
522                         } else { // create one...
523                             var part = new egCore.idl.bmp();
524                             part.id( --$scope.new_part_id );
525                             part.isnew( true );
526                             part.label( $scope.part );
527                             part.record( $scope.callNumber.record() );
528                             $scope.copy.parts([part]);
529                             $scope.copy.ischanged(1);
530                         }
531                     } else {
532                         $scope.copy.parts([]);
533                     }
534                     $scope.copy.ischanged(1);
535                 }
536                 $scope.$watch('part', $scope.updatePart);
537
538                 $scope.barcode = $scope.copy.barcode();
539                 $scope.copy_number = $scope.copy.copy_number();
540
541                 if ($scope.copy.parts()) {
542                     $scope.part = $scope.copy.parts()[0];
543                     if ($scope.part) $scope.part = $scope.part.label();
544                 };
545
546                 $scope.parts = [];
547                 $scope.part_list = [];
548
549                 itemSvc.get_parts($scope.callNumber.record()).then(function(list){
550                     $scope.part_list = list;
551                     angular.forEach(list, function(p){ $scope.parts.push(p.label()) });
552                     $scope.parts = angular.copy($scope.parts);
553                 });
554
555             }
556         ]
557
558     }
559 })
560
561 .directive("egVolRow", function () {
562     return {
563         restrict: 'E',
564         replace: true,
565         transclude: true,
566         template:
567             '<div class="row">'+
568                 '<div class="col-xs-2">'+
569                     '<select ng-disabled="record == 0" class="form-control" ng-model="classification" ng-change="updateClassification()" ng-options="cl.name() for cl in classification_list"/>'+
570                 '</div>'+
571                 '<div class="col-xs-1">'+
572                     '<select ng-disabled="record == 0" class="form-control" ng-model="prefix" ng-change="updatePrefix()" ng-options="p.label() for p in prefix_list"/>'+
573                 '</div>'+
574                 '<div class="col-xs-2">'+
575                     '<input ng-disabled="record == 0" class="form-control" type="text" ng-change="updateLabel()" ng-model="label"/>'+
576                     '<div class="label label-danger" ng-if="empty_label">{{empty_label_string}}</div>'+
577                 '</div>'+
578                 '<div class="col-xs-1">'+
579                     '<select ng-disabled="record == 0" class="form-control" ng-model="suffix" ng-change="updateSuffix()" ng-options="s.label() for s in suffix_list"/>'+
580                 '</div>'+
581                 '<div ng-hide="onlyVols" class="col-xs-1"><input ng-disabled="record == 0" class="form-control" type="number" ng-model="copy_count" min="{{orig_copy_count}}" ng-change="changeCPCount()"></div>'+
582                 '<div ng-hide="onlyVols" class="col-xs-5">'+
583                     '<eg-vol-copy-edit record="{{record}}" ng-repeat="cp in copies track by idTracker(cp)" focus-next="focusNextBarcode" copy="cp" call-number="callNumber"></eg-vol-copy-edit>'+
584                 '</div>'+
585             '</div>',
586
587         scope: {focusNext: "=", allcopies: "=", copies: "=", onlyVols: "=", record: "@" },
588         controller : ['$scope','itemSvc','egCore',
589             function ( $scope , itemSvc , egCore ) {
590                 $scope.callNumber =  $scope.copies[0].call_number();
591                 if (!$scope.callNumber.label()) $scope.callNumber.emtpy_label = true;
592
593                 $scope.empty_label = false;
594                 $scope.empty_label_string = window.empty_label_string;
595
596                 $scope.idTracker = function (x) { if (x && x.id) return x.id() };
597
598                 // XXX $() is not working! arg
599                 $scope.focusNextBarcode = function (i, prev_bc) {
600                     var n;
601                     var yep = false;
602                     angular.forEach($scope.copies, function (cp) {
603                         if (n) return;
604
605                         if (cp.id() == i) {
606                             yep = true;
607                             return;
608                         }
609
610                         if (yep) n = cp.id();
611                     });
612
613                     if (n) {
614                         var next = '#' + $scope.callNumber.id() + '_' + n;
615                         var el = $(next);
616                         if (el) {
617                             if (!itemSvc.currently_generating) el.focus();
618                             if (prev_bc && itemSvc.auto_gen_barcode && el.val() == "") {
619                                 itemSvc.nextBarcode(prev_bc).then(function(bc){
620                                     el.focus();
621                                     el.val(bc);
622                                     el.trigger('change');
623                                 });
624                             } else {
625                                 itemSvc.currently_generating = false;
626                             }
627                         }
628                     } else {
629                         $scope.focusNext($scope.callNumber.id(),prev_bc)
630                     }
631                 }
632
633                 $scope.suffix_list = [];
634                 itemSvc.get_suffixes($scope.callNumber.owning_lib()).then(function(list){
635                     $scope.suffix_list = list;
636                     $scope.$watch('callNumber.suffix()', function (v) {
637                         if (angular.isObject(v)) v = v.id();
638                         $scope.suffix = $scope.suffix_list.filter( function (s) {
639                             return s.id() == v;
640                         })[0];
641                     });
642
643                 });
644                 $scope.updateSuffix = function () {
645                     angular.forEach($scope.copies, function(cp) {
646                         cp.call_number().suffix($scope.suffix);
647                         cp.call_number().ischanged(1);
648                     });
649                 }
650
651                 $scope.prefix_list = [];
652                 itemSvc.get_prefixes($scope.callNumber.owning_lib()).then(function(list){
653                     $scope.prefix_list = list;
654                     $scope.$watch('callNumber.prefix()', function (v) {
655                         if (angular.isObject(v)) v = v.id();
656                         $scope.prefix = $scope.prefix_list.filter(function (p) {
657                             return p.id() == v;
658                         })[0];
659                     });
660
661                 });
662                 $scope.updatePrefix = function () {
663                     angular.forEach($scope.copies, function(cp) {
664                         cp.call_number().prefix($scope.prefix);
665                         cp.call_number().ischanged(1);
666                     });
667                 }
668                 $scope.$watch('callNumber.owning_lib()', function(oldLib, newLib) {
669                     if (oldLib == newLib) return;
670                     var currentPrefix = $scope.callNumber.prefix();
671                     if (angular.isObject(currentPrefix)) currentPrefix = currentPrefix.id();
672                     itemSvc.get_prefixes($scope.callNumber.owning_lib()).then(function(list){
673                         $scope.prefix_list = list;
674                         var newPrefixId = $scope.prefix_list.filter(function (p) {
675                             return p.id() == currentPrefix;
676                         })[0] || -1;
677                         if (newPrefixId.id) newPrefixId = newPrefixId.id();
678                         $scope.prefix = $scope.prefix_list.filter(function (p) {
679                             return p.id() == newPrefixId;
680                         })[0];
681                         if ($scope.newPrefixId != currentPrefix) {
682                             $scope.callNumber.prefix($scope.prefix);
683                         }
684                     });
685                     var currentSuffix = $scope.callNumber.suffix();
686                     if (angular.isObject(currentSuffix)) currentSuffix = currentSuffix.id();
687                     itemSvc.get_suffixes($scope.callNumber.owning_lib()).then(function(list){
688                         $scope.suffix_list = list;
689                         var newSuffixId = $scope.suffix_list.filter(function (s) {
690                             return s.id() == currentSuffix;
691                         })[0] || -1;
692                         if (newSuffixId.id) newSuffixId = newSuffixId.id();
693                         $scope.suffix = $scope.suffix_list.filter(function (s) {
694                             return s.id() == newSuffixId;
695                         })[0];
696                         if ($scope.newSuffixId != currentSuffix) {
697                             $scope.callNumber.suffix($scope.suffix);
698                         }
699                     });
700                 });
701
702                 $scope.classification_list = [];
703                 itemSvc.get_classifications().then(function(list){
704                     $scope.classification_list = list;
705                     $scope.$watch('callNumber.label_class()', function (v) {
706                         if (angular.isObject(v)) v = v.id();
707                         $scope.classification = $scope.classification_list.filter(function (c) {
708                             return c.id() == v;
709                         })[0];
710                     });
711
712                 });
713                 $scope.updateClassification = function () {
714                     angular.forEach($scope.copies, function(cp) {
715                         cp.call_number().label_class($scope.classification);
716                         cp.call_number().ischanged(1);
717                     });
718                 }
719
720                 $scope.updateLabel = function () {
721                     if ($scope.label == '') {
722                         $scope.callNumber.empty_label = $scope.empty_label = true;
723                     } else {
724                         $scope.callNumber.empty_label = $scope.empty_label = false;
725                     }
726                     angular.forEach($scope.copies, function(cp) {
727                         cp.call_number().label($scope.label);
728                         cp.call_number().ischanged(1);
729                     });
730                 }
731
732                 $scope.$watch('callNumber.label()', function (v) {
733                     $scope.label = v;
734                 });
735
736                 $scope.prefix = $scope.callNumber.prefix();
737                 $scope.suffix = $scope.callNumber.suffix();
738                 $scope.classification = $scope.callNumber.label_class();
739                 $scope.label = $scope.callNumber.label();
740
741                 $scope.copy_count = $scope.copies.length;
742                 $scope.orig_copy_count = $scope.copy_count;
743
744                 $scope.changeCPCount = function () {
745                     while ($scope.copy_count > $scope.copies.length) {
746                         var cp = itemSvc.generateNewCopy(
747                             $scope.callNumber,
748                             $scope.callNumber.owning_lib(),
749                             $scope.fast_add,
750                             true
751                         );
752                         $scope.copies.push( cp );
753                         $scope.allcopies.push( cp );
754
755                     }
756
757                     if ($scope.copy_count >= $scope.orig_copy_count) {
758                         var how_many = $scope.copies.length - $scope.copy_count;
759                         if (how_many > 0) {
760                             var dead = $scope.copies.splice($scope.copy_count,how_many);
761                             $scope.callNumber.copies($scope.copies);
762
763                             // Trimming the global list is a bit more tricky
764                             angular.forEach( dead, function (d) {
765                                 angular.forEach( $scope.allcopies, function (l, i) { 
766                                     if (l === d) $scope.allcopies.splice(i,1);
767                                 });
768                             });
769                         }
770                     }
771                 }
772
773             }
774         ]
775
776     }
777 })
778
779 .directive("egVolEdit", function () {
780     return {
781         restrict: 'E',
782         replace: true,
783         template:
784             '<div class="row">'+
785                 '<div class="col-xs-1"><eg-org-selector alldisabled="{{record == 0}}" selected="owning_lib" disable-test="cant_have_vols"></eg-org-selector></div>'+
786                 '<div class="col-xs-1"><input ng-disabled="record == 0" class="form-control" type="number" min="{{orig_cn_count}}" ng-model="cn_count" ng-change="changeCNCount()"/></div>'+
787                 '<div class="col-xs-10">'+
788                     '<eg-vol-row only-vols="onlyVols" record="{{record}}"'+
789                         'ng-repeat="(cn,copies) in struct" '+
790                         'focus-next="focusNextFirst" copies="copies" allcopies="allcopies">'+
791                     '</eg-vol-row>'+
792                 '</div>'+
793             '</div>',
794
795         scope: { focusNext: "=", allcopies: "=", struct: "=", lib: "@", record: "@", onlyVols: "=" },
796         controller : ['$scope','itemSvc','egCore',
797             function ( $scope , itemSvc , egCore ) {
798                 $scope.first_cn = Object.keys($scope.struct)[0];
799                 $scope.full_cn = $scope.struct[$scope.first_cn][0].call_number();
800
801                 $scope.defaults = {};
802                 egCore.hatch.getItem('cat.copy.defaults').then(function(t) {
803                     if (t) {
804                         $scope.defaults = t;
805                     }
806                 });
807
808                 $scope.focusNextFirst = function(prev_cn,prev_bc) {
809                     var n;
810                     var yep = false;
811                     angular.forEach(Object.keys($scope.struct).sort(), function (cn) {
812                         if (n) return;
813
814                         if (cn == prev_cn) {
815                             yep = true;
816                             return;
817                         }
818
819                         if (yep) n = cn;
820                     });
821
822                     if (n) {
823                         var next = '#' + n + '_' + $scope.struct[n][0].id();
824                         var el = $(next);
825                         if (el) {
826                             if (!itemSvc.currently_generating) el.focus();
827                             if (prev_bc && itemSvc.auto_gen_barcode && el.val() == "") {
828                                 itemSvc.nextBarcode(prev_bc).then(function(bc){
829                                     el.focus();
830                                     el.val(bc);
831                                     el.trigger('change');
832                                 });
833                             } else {
834                                 itemSvc.currently_generating = false;
835                             }
836                         }
837                     } else {
838                         $scope.focusNext($scope.lib, prev_bc);
839                     }
840                 }
841
842                 $scope.cn_count = Object.keys($scope.struct).length;
843                 $scope.orig_cn_count = $scope.cn_count;
844
845                 $scope.owning_lib = egCore.org.get($scope.lib);
846                 $scope.$watch('owning_lib', function (oldLib, newLib) {
847                     if (oldLib == newLib) return;
848                     angular.forEach( Object.keys($scope.struct), function (cn) {
849                         $scope.struct[cn][0].call_number().owning_lib( $scope.owning_lib.id() );
850                         $scope.struct[cn][0].call_number().ischanged(1);
851                     });
852                 });
853
854                 $scope.cant_have_vols = function (id) { return !egCore.org.CanHaveVolumes(id); };
855
856                 $scope.$watch('cn_count', function (n) {
857                     var o = Object.keys($scope.struct).length;
858                     if (n > o) { // adding
859                         for (var i = o; o < n; o++) {
860                             var cn = new egCore.idl.acn();
861                             cn.id( --itemSvc.new_cn_id );
862                             cn.isnew( true );
863                             cn.prefix( $scope.defaults.prefix || -1 );
864                             cn.suffix( $scope.defaults.suffix || -1 );
865                             cn.label_class( $scope.defaults.classification || 1 );
866                             cn.owning_lib( $scope.owning_lib.id() );
867                             cn.record( $scope.full_cn.record() );
868
869                             var cp = itemSvc.generateNewCopy(
870                                 cn,
871                                 $scope.owning_lib.id(),
872                                 $scope.fast_add,
873                                 true
874                             );
875
876                             $scope.struct[cn.id()] = [cp];
877                             $scope.allcopies.push(cp);
878                             if (!$scope.defaults.classification) {
879                                 egCore.org.settings(
880                                     ['cat.default_classification_scheme'],
881                                     cn.owning_lib()
882                                 ).then(function (val) {
883                                     cn.label_class(val['cat.default_classification_scheme']);
884                                 });
885                             }
886                         }
887                     } else if (n < o && n >= $scope.orig_cn_count) { // removing
888                         var how_many = o - n;
889                         var list = Object
890                                 .keys($scope.struct)
891                                 .sort(function(a, b){return parseInt(a)-parseInt(b)})
892                                 .filter(function(x){ return parseInt(x) <= 0 });
893                         for (var i = 0; i < how_many; i++) {
894                             // Trimming the global list is a bit more tricky
895                             angular.forEach($scope.struct[list[i]], function (d) {
896                                 angular.forEach( $scope.allcopies, function (l, j) { 
897                                     if (l === d) $scope.allcopies.splice(j,1);
898                                 });
899                             });
900                             delete $scope.struct[list[i]];
901                         }
902                     }
903                 });
904             }
905         ]
906
907     }
908 })
909
910 /**
911  * Edit controller!
912  */
913 .controller('EditCtrl', 
914        ['$scope','$q','$window','$routeParams','$location','$timeout','egCore','egNet','egGridDataProvider','itemSvc','$uibModal',
915 function($scope , $q , $window , $routeParams , $location , $timeout , egCore , egNet , egGridDataProvider , itemSvc , $uibModal) {
916
917     $scope.forms = {}; // Accessed by t_attr_edit.tt2
918     $scope.i18n = egCore.i18n;
919
920     $scope.defaults = { // If defaults are not set at all, allow everything
921         barcode_checkdigit : false,
922         auto_gen_barcode : false,
923         statcats : true,
924         copy_notes : true,
925         copy_tags : true,
926         attributes : {
927             status : true,
928             loan_duration : true,
929             fine_level : true,
930             cost : true,
931             alerts : true,
932             deposit : true,
933             deposit_amount : true,
934             opac_visible : true,
935             price : true,
936             circulate : true,
937             mint_condition : true,
938             circ_lib : true,
939             ref : true,
940             circ_modifier : true,
941             circ_as_type : true,
942             location : true,
943             holdable : true,
944             age_protect : true,
945             floating : true,
946             alert_message : true
947         }
948     };
949
950     $scope.new_lib_to_add = egCore.org.get(egCore.auth.user().ws_ou());
951     $scope.changeNewLib = function (org) {
952         $scope.new_lib_to_add = org;
953     }
954     $scope.addLibToStruct = function () {
955         var newLib = $scope.new_lib_to_add;
956         var cn = new egCore.idl.acn();
957         cn.id( --itemSvc.new_cn_id );
958         cn.isnew( true );
959         cn.prefix( $scope.defaults.prefix || -1 );
960         cn.suffix( $scope.defaults.suffix || -1 );
961         cn.label_class( $scope.defaults.classification || 1 );
962         cn.owning_lib( newLib.id() );
963         cn.record( $scope.record_id );
964
965         var cp = itemSvc.generateNewCopy(
966             cn,
967             newLib.id(),
968             $scope.fast_add,
969             true
970         );
971
972         $scope.data.addCopy(cp);
973
974         if (!$scope.defaults.classification) {
975             egCore.org.settings(
976                 ['cat.default_classification_scheme'],
977                 cn.owning_lib()
978             ).then(function (val) {
979                 cn.label_class(val['cat.default_classification_scheme']);
980             });
981         }
982     }
983
984     $scope.embedded = ($routeParams.mode && $routeParams.mode == 'embedded') ? true : false;
985     $scope.edit_templates = ($location.path().match(/edit_template/)) ? true : false;
986
987     $scope.saveDefaults = function () {
988         egCore.hatch.setItem('cat.copy.defaults', $scope.defaults);
989     }
990
991     $scope.fetchDefaults = function () {
992         egCore.hatch.getItem('cat.copy.defaults').then(function(t) {
993             if (t) {
994                 $scope.defaults = t;
995                 if (!$scope.batch) $scope.batch = {};
996                 $scope.batch.classification = $scope.defaults.classification;
997                 $scope.batch.prefix = $scope.defaults.prefix;
998                 $scope.batch.suffix = $scope.defaults.suffix;
999                 $scope.working.statcat_filter = $scope.defaults.statcat_filter;
1000                 if (
1001                         typeof $scope.defaults.statcat_filter == 'object' &&
1002                         Object.keys($scope.defaults.statcat_filter).length > 0
1003                    ) {
1004                     // want fieldmapper object here...
1005                     $scope.defaults.statcat_filter =
1006                          egCore.idl.Clone($scope.defaults.statcat_filter);
1007                     // ... and ID here
1008                     $scope.working.statcat_filter = $scope.defaults.statcat_filter.id();
1009                 }
1010                 if ($scope.defaults.always_volumes) $scope.show_vols = true;
1011                 if ($scope.defaults.barcode_checkdigit) itemSvc.barcode_checkdigit = true;
1012                 if ($scope.defaults.auto_gen_barcode) itemSvc.auto_gen_barcode = true;
1013             }
1014         });
1015     }
1016     $scope.fetchDefaults();
1017
1018     $scope.$watch('defaults.statcat_filter', function() {
1019         $scope.saveDefaults();
1020     });
1021     $scope.$watch('defaults.auto_gen_barcode', function (n,o) {
1022         itemSvc.auto_gen_barcode = n
1023     });
1024
1025     $scope.$watch('defaults.barcode_checkdigit', function (n,o) {
1026         itemSvc.barcode_checkdigit = n
1027     });
1028
1029     $scope.dirty = false;
1030     $scope.$watch('dirty',
1031         function(newVal, oldVal) {
1032             if (newVal && newVal != oldVal) {
1033                 $($window).on('beforeunload.edit', function(){
1034                     return 'There is unsaved data!'
1035                 });
1036             } else {
1037                 $($window).off('beforeunload.edit');
1038             }
1039         }
1040     );
1041
1042     $scope.only_vols = false;
1043     $scope.show_vols = true;
1044     $scope.show_copies = true;
1045
1046     $scope.tracker = function (x,f) { if (x) return x[f]() };
1047     $scope.idTracker = function (x) { if (x) return $scope.tracker(x,'id') };
1048     $scope.cant_have_vols = function (id) { return !egCore.org.CanHaveVolumes(id); };
1049
1050     $scope.orgById = function (id) { return egCore.org.get(id) }
1051     $scope.statusById = function (id) {
1052         return $scope.status_list.filter( function (s) { return s.id() == id } )[0];
1053     }
1054     $scope.locationById = function (id) {
1055         return $scope.location_cache[''+id];
1056     }
1057
1058     $scope.workingToComplete = function () {
1059         angular.forEach( $scope.workingGridControls.selectedItems(), function (c) {
1060             angular.forEach( itemSvc.copies, function (w, i) {
1061                 if (c === w)
1062                     $scope.completed_copies = $scope.completed_copies.concat(itemSvc.copies.splice(i,1));
1063             });
1064         });
1065
1066         return true;
1067     }
1068
1069     $scope.completeToWorking = function () {
1070         angular.forEach( $scope.completedGridControls.selectedItems(), function (c) {
1071             angular.forEach( $scope.completed_copies, function (w, i) {
1072                 if (c === w)
1073                     itemSvc.copies = itemSvc.copies.concat($scope.completed_copies.splice(i,1));
1074             });
1075         });
1076
1077         return true;
1078     }
1079
1080     createSimpleUpdateWatcher = function (field,exclude_copies_with_one_of_these_values) {
1081         return $scope.$watch('working.' + field, function () {
1082             var newval = $scope.working[field];
1083
1084             if (typeof newval != 'undefined') {
1085                 if (angular.isObject(newval)) { // we'll use the pkey
1086                     if (newval.id) newval = newval.id();
1087                     else if (newval.code) newval = newval.code();
1088                 }
1089
1090                 if (""+newval == "" || newval == null) {
1091                     $scope.working[field] = undefined;
1092                     newval = null;
1093                 }
1094
1095                 if ($scope.workingGridControls && $scope.workingGridControls.selectedItems) {
1096                     angular.forEach(
1097                         $scope.workingGridControls.selectedItems(),
1098                         function (cp) {
1099                             if (exclude_copies_with_one_of_these_values
1100                                 && exclude_copies_with_one_of_these_values.indexOf(cp[field](),0) > -1) {
1101                                 return;
1102                             }
1103                             if (cp[field]() !== newval) {
1104                                 cp[field](newval);
1105                                 cp.ischanged(1);
1106                                 $scope.dirty = true;
1107                             }
1108                         }
1109                     );
1110                 }
1111             }
1112         });
1113     }
1114
1115     $scope.working = {
1116         statcats: {},
1117         statcat_filter: undefined
1118     };
1119
1120     $scope.statcatUpdate = function (id) {
1121         var newval = $scope.working.statcats[id];
1122
1123         if (typeof newval != 'undefined') {
1124             if (angular.isObject(newval)) { // we'll use the pkey
1125                 newval = newval.id();
1126             }
1127     
1128             if (""+newval == "" || newval == null) {
1129                 $scope.working.statcats[id] = undefined;
1130                 newval = null;
1131             }
1132     
1133             if (!$scope.in_item_select && $scope.workingGridControls && $scope.workingGridControls.selectedItems) {
1134                 angular.forEach(
1135                     $scope.workingGridControls.selectedItems(),
1136                     function (cp) {
1137                         $scope.dirty = true;
1138
1139                         cp.stat_cat_entries(
1140                             angular.forEach( cp.stat_cat_entries(), function (e) {
1141                                 if (e.stat_cat() == id) { // mark deleted
1142                                     e.isdeleted(1);
1143                                 }
1144                             })
1145                         );
1146     
1147                         if (newval) {
1148                             var e = new egCore.idl.asce();
1149                             e.isnew( 1 );
1150                             e.stat_cat( id );
1151                             e.id(newval);
1152
1153                             cp.stat_cat_entries(
1154                                 cp.stat_cat_entries() ?
1155                                     cp.stat_cat_entries().concat([ e ]) :
1156                                     [ e ]
1157                             );
1158
1159                         }
1160
1161                         // trim out all deleted ones; the API used to
1162                         // do the update doesn't actually consult
1163                         // isdeleted for stat cat entries
1164                         cp.stat_cat_entries(
1165                             cp.stat_cat_entries().filter(function (e) {
1166                                 return !Boolean(e.isdeleted());
1167                             })
1168                         );
1169    
1170                         cp.ischanged(1);
1171                     }
1172                 );
1173             }
1174         }
1175     }
1176
1177     var dataKey = $routeParams.dataKey;
1178     console.debug('dataKey: ' + dataKey);
1179
1180     if ((dataKey && dataKey.length > 0) || $scope.edit_templates) {
1181
1182         $scope.templates = {};
1183         $scope.template_name = '';
1184         $scope.template_name_list = [];
1185
1186         $scope.fetchTemplates = function () {
1187             itemSvc.get_acp_templates().then(function(t) {
1188                 if (t) {
1189                     $scope.templates = t;
1190                     $scope.template_name_list = Object.keys(t).sort();
1191                 }
1192             });
1193             egCore.hatch.getItem('cat.copy.last_template').then(function(t) {
1194                 if (t) $scope.template_name = t;
1195             });
1196         }
1197         $scope.fetchTemplates();
1198
1199         $scope.applyTemplate = function (n) {
1200             angular.forEach($scope.templates[n], function (v,k) {
1201                 if (k == 'circ_lib') {
1202                     $scope.working[k] = egCore.org.get(v);
1203                 } else if (!angular.isObject(v)) {
1204                     $scope.working[k] = angular.copy(v);
1205                 } else {
1206                     angular.forEach(v, function (sv,sk) {
1207                         if (k == 'callnumber') {
1208                             angular.forEach(v, function (cnv,cnk) {
1209                                 $scope.batch[cnk] = cnv;
1210                             });
1211                             $scope.applyBatchCNValues();
1212                         } else {
1213                             $scope.working[k][sk] = angular.copy(sv);
1214                             if (k == 'statcats') $scope.statcatUpdate(sk);
1215                         }
1216                     });
1217                 }
1218             });
1219             egCore.hatch.setItem('cat.copy.last_template', n);
1220         }
1221
1222         $scope.copytab = 'working';
1223         $scope.tab = 'edit';
1224         $scope.summaryRecord = null;
1225         $scope.record_id = null;
1226         $scope.data = {};
1227         $scope.completed_copies = [];
1228         $scope.location_orgs = [];
1229         $scope.location_cache = {};
1230         $scope.statcats = [];
1231         if (!$scope.batch) $scope.batch = {};
1232
1233         $scope.applyBatchCNValues = function () {
1234             if ($scope.data.tree) {
1235                 angular.forEach($scope.data.tree, function(cn_hash) {
1236                     angular.forEach(cn_hash, function(copies) {
1237                         angular.forEach(copies, function(cp) {
1238                             if (typeof $scope.batch.classification != 'undefined' && $scope.batch.classification != '') {
1239                                 var label_class = $scope.classification_list.filter(function(p){ return p.id() == $scope.batch.classification })[0];
1240                                 cp.call_number().label_class(label_class);
1241                                 cp.call_number().ischanged(1);
1242                                 $scope.dirty = true;
1243                             }
1244                             if (typeof $scope.batch.prefix != 'undefined' && $scope.batch.prefix != '') {
1245                                 var prefix = $scope.prefix_list.filter(function(p){ return p.id() == $scope.batch.prefix })[0];
1246                                 cp.call_number().prefix(prefix);
1247                                 cp.call_number().ischanged(1);
1248                                 $scope.dirty = true;
1249                             }
1250                             if (typeof $scope.batch.label != 'undefined' && $scope.batch.label != '') {
1251                                 cp.call_number().label($scope.batch.label);
1252                                 cp.call_number().ischanged(1);
1253                                 $scope.dirty = true;
1254                             }
1255                             if (typeof $scope.batch.suffix != 'undefined' && $scope.batch.suffix != '') {
1256                                 var suffix = $scope.suffix_list.filter(function(p){ return p.id() == $scope.batch.suffix })[0];
1257                                 cp.call_number().suffix(suffix);
1258                                 cp.call_number().ischanged(1);
1259                                 $scope.dirty = true;
1260                             }
1261                         });
1262                     });
1263                 });
1264             }
1265         }
1266
1267         $scope.clearWorking = function () {
1268             angular.forEach($scope.working, function (v,k,o) {
1269                 if (!angular.isObject(v)) {
1270                     if (typeof v != 'undefined')
1271                         $scope.working[k] = undefined;
1272                 } else if (k != 'circ_lib') {
1273                     angular.forEach(v, function (sv,sk) {
1274                         if (typeof v != 'undefined')
1275                             $scope.working[k][sk] = undefined;
1276                     });
1277                 }
1278             });
1279             $scope.working.circ_lib = undefined; // special
1280         }
1281
1282         $scope.completedGridDataProvider = egGridDataProvider.instance({
1283             get : function(offset, count) {
1284                 //return provider.arrayNotifier(itemSvc.copies, offset, count);
1285                 return this.arrayNotifier($scope.completed_copies, offset, count);
1286             }
1287         });
1288
1289         $scope.completedGridControls = {};
1290
1291         $scope.workingGridDataProvider = egGridDataProvider.instance({
1292             get : function(offset, count) {
1293                 //return provider.arrayNotifier(itemSvc.copies, offset, count);
1294                 return this.arrayNotifier(itemSvc.copies, offset, count);
1295             }
1296         });
1297
1298         $scope.workingGridControls = {};
1299         $scope.add_vols_copies = false;
1300         $scope.is_fast_add = false;
1301
1302         egNet.request(
1303             'open-ils.actor',
1304             'open-ils.actor.anon_cache.get_value',
1305             dataKey, 'edit-these-copies'
1306         ).then(function (data) {
1307
1308             if (data) {
1309                 if (data.hide_vols && !$scope.defaults.always_volumes) $scope.show_vols = false;
1310                 if (data.hide_copies) {
1311                     $scope.show_copies = false;
1312                     $scope.only_vols = true;
1313                 }
1314
1315                 $scope.record_id = data.record_id;
1316
1317                 function fetchRaw () {
1318                     if (!$scope.only_vols) $scope.dirty = true;
1319                     $scope.add_vols_copies = true;
1320
1321                     /* data.raw data structure looks like this:
1322                      * [{
1323                      *      callnumber : $cn_id, // optional, to add a copy to a cn
1324                      *      owner      : $org, // optional, defaults to ws_ou
1325                      *      label      : $cn_label, // optional, to supply a label on a new cn
1326                      *      barcode    : $cp_barcode // optional, to supply a barcode on a new cp
1327                      *      fast_add   : boolean // optional, to specify whether this came
1328                      *                              in as a fast add
1329                      * },...]
1330                      * 
1331                      * All can be left out and a completely empty vol/copy combo will be vivicated.
1332                      */
1333
1334                     angular.forEach(
1335                         data.raw,
1336                         function (proto) {
1337                             if (proto.fast_add) $scope.is_fast_add = true;
1338                             if (proto.callnumber) {
1339                                 return egCore.pcrud.retrieve('acn', proto.callnumber)
1340                                 .then(function(cn) {
1341                                     var cp = new itemSvc.generateNewCopy(
1342                                         cn,
1343                                         proto.owner || egCore.auth.user().ws_ou(),
1344                                         $scope.is_fast_add,
1345                                         ((!$scope.only_vols) ? true : false)
1346                                     );
1347
1348                                     if (proto.barcode) {
1349                                         cp.barcode( proto.barcode );
1350                                         cp.empty_barcode = false;
1351                                     }
1352
1353                                     itemSvc.addCopy(cp)
1354                                 });
1355                             } else {
1356                                 var cn = new egCore.idl.acn();
1357                                 cn.id( --itemSvc.new_cn_id );
1358                                 cn.isnew( true );
1359                                 cn.prefix( $scope.defaults.prefix || -1 );
1360                                 cn.suffix( $scope.defaults.suffix || -1 );
1361                                 cn.owning_lib( proto.owner || egCore.auth.user().ws_ou() );
1362                                 cn.record( $scope.record_id );
1363                                 egCore.org.settings(
1364                                     ['cat.default_classification_scheme'],
1365                                     cn.owning_lib()
1366                                 ).then(function (val) {
1367                                     cn.label_class(
1368                                         $scope.defaults.classification ||
1369                                         val['cat.default_classification_scheme'] ||
1370                                         1
1371                                     );
1372                                     if (proto.label) {
1373                                         cn.label( proto.label );
1374                                     } else {
1375                                         egCore.net.request(
1376                                             'open-ils.cat',
1377                                             'open-ils.cat.biblio.record.marc_cn.retrieve',
1378                                             $scope.record_id,
1379                                             cn.label_class()
1380                                         ).then(function(cn_array) {
1381                                             if (cn_array.length > 0) {
1382                                                 for (var field in cn_array[0]) {
1383                                                     cn.label( cn_array[0][field] );
1384                                                     break;
1385                                                 }
1386                                             }
1387                                         });
1388                                     }
1389                                 });
1390
1391                                 var cp = new itemSvc.generateNewCopy(
1392                                     cn,
1393                                     proto.owner || egCore.auth.user().ws_ou(),
1394                                     $scope.is_fast_add,
1395                                     true
1396                                 );
1397
1398                                 if (proto.barcode) {
1399                                     cp.barcode( proto.barcode );
1400                                     cp.empty_barcode = false;
1401                                 }
1402
1403                                 itemSvc.addCopy(cp)
1404                             }
1405     
1406                         }
1407                     );
1408
1409                     return itemSvc.copies;
1410                 }
1411
1412                 if (data.copies && data.copies.length)
1413                     return itemSvc.fetchIds(data.copies).then(fetchRaw);
1414
1415                 return fetchRaw();
1416
1417             }
1418
1419         }).then( function() {
1420             $scope.data = itemSvc;
1421             $scope.workingGridDataProvider.refresh();
1422         });
1423
1424         $scope.can_save = false;
1425         function check_saveable () {
1426             var can_save = true;
1427             angular.forEach(
1428                 itemSvc.copies,
1429                 function (i) {
1430                     if (i.duplicate_barcode || i.empty_barcode || i.call_number().empty_label)
1431                         can_save = false;
1432                 }
1433             );
1434             if ($scope.forms.myForm && $scope.forms.myForm.$invalid) {
1435                 can_save = false;
1436             }
1437             $scope.can_save = can_save;
1438         }
1439
1440         $scope.disableSave = function () {
1441             check_saveable();
1442             return !$scope.can_save;
1443         }
1444
1445         $scope.focusNextFirst = function(prev_lib,prev_bc) {
1446             var n;
1447             var yep = false;
1448             angular.forEach(Object.keys($scope.data.tree).sort(), function (lib) {
1449                 if (n) return;
1450
1451                 if (lib == prev_lib) {
1452                     yep = true;
1453                     return;
1454                 }
1455
1456                 if (yep) n = lib;
1457             });
1458
1459             if (n) {
1460                 var first_cn = Object.keys($scope.data.tree[n])[0];
1461                 var next = '#' + first_cn + '_' + $scope.data.tree[n][first_cn][0].id();
1462                 var el = $(next);
1463                 if (el) {
1464                     if (!itemSvc.currently_generating) el.focus();
1465                     if (prev_bc && itemSvc.auto_gen_barcode && el.val() == "") {
1466                         itemSvc.nextBarcode(prev_bc).then(function(bc){
1467                             el.focus();
1468                             el.val(bc);
1469                             el.trigger('change');
1470                         });
1471                     } else {
1472                         itemSvc.currently_generating = false;
1473                     }
1474                 }
1475             }
1476         }
1477
1478         $scope.in_item_select = false;
1479         $scope.afterItemSelect = function() { $scope.in_item_select = false };
1480         $scope.handleItemSelect = function (item_list) {
1481             if (item_list && item_list.length > 0) {
1482                 $scope.in_item_select = true;
1483
1484                 angular.forEach(Object.keys($scope.defaults.attributes), function (attr) {
1485
1486                     var value_hash = {};
1487                     angular.forEach(item_list, function (item) {
1488                         if (item[attr]) {
1489                             var v = item[attr]()
1490                             if (angular.isObject(v)) {
1491                                 if (v.id) v = v.id();
1492                                 else if (v.code) v = v.code();
1493                             }
1494                             value_hash[v] = 1;
1495                         }
1496                     });
1497
1498                     if (Object.keys(value_hash).length == 1) {
1499                         if (attr == 'circ_lib') {
1500                             $scope.working[attr] = egCore.org.get(item_list[0][attr]());
1501                         } else {
1502                             $scope.working[attr] = item_list[0][attr]();
1503                         }
1504                     } else {
1505                         $scope.working[attr] = undefined;
1506                     }
1507                 });
1508
1509                 angular.forEach($scope.statcats, function (sc) {
1510
1511                     var counter = -1;
1512                     var value_hash = {};
1513                     var none = false;
1514                     angular.forEach(item_list, function (item) {
1515                         if (item.stat_cat_entries()) {
1516                             if (item.stat_cat_entries().length > 0) {
1517                                 var right_sc = item.stat_cat_entries().filter(function (e) {
1518                                     return e.stat_cat() == sc.id() && !Boolean(e.isdeleted());
1519                                 });
1520
1521                                 if (right_sc.length > 0) {
1522                                     value_hash[right_sc[0].id()] = right_sc[0].id();
1523                                 } else {
1524                                     none = true;
1525                                 }
1526                             }
1527                         } else {
1528                             none = true;
1529                         }
1530                     });
1531
1532                     if (!none && Object.keys(value_hash).length == 1) {
1533                         $scope.working.statcats[sc.id()] = value_hash[Object.keys(value_hash)[0]];
1534                     } else {
1535                         $scope.working.statcats[sc.id()] = undefined;
1536                     }
1537                 });
1538
1539             } else {
1540                 $scope.clearWorking();
1541             }
1542
1543         }
1544
1545         $scope.$watch('data.copies.length', function () {
1546             if ($scope.data.copies) {
1547                 var base_orgs = $scope.data.copies.map(function(cp){
1548                     return cp.circ_lib()
1549                 }).concat(
1550                     $scope.data.copies.map(function(cp){
1551                         return cp.call_number().owning_lib()
1552                     })
1553                 ).concat(
1554                     [egCore.auth.user().ws_ou()]
1555                 ).filter(function(e,i,a){
1556                     return a.lastIndexOf(e) === i;
1557                 });
1558
1559                 var all_orgs = [];
1560                 angular.forEach(base_orgs, function(o) {
1561                     all_orgs = all_orgs.concat( egCore.org.fullPath(o, true) );
1562                 });
1563
1564                 var final_orgs = all_orgs.filter(function(e,i,a){
1565                     return a.lastIndexOf(e) === i;
1566                 }).sort(function(a, b){return parseInt(a)-parseInt(b)});
1567
1568                 if ($scope.location_orgs.toString() != final_orgs.toString()) {
1569                     $scope.location_orgs = final_orgs;
1570                     if ($scope.location_orgs.length) {
1571                         itemSvc.get_locations($scope.location_orgs).then(function(list){
1572                             angular.forEach(list, function(l) {
1573                                 $scope.location_cache[ ''+l.id() ] = l;
1574                             });
1575                             $scope.location_list = list;
1576                         });
1577
1578                         $scope.statcat_filter_list = [];
1579                         angular.forEach($scope.location_orgs, function (o) {
1580                             $scope.statcat_filter_list.push(egCore.org.get(o));
1581                         });
1582
1583                         itemSvc.get_statcats($scope.location_orgs).then(function(list){
1584                             $scope.statcats = list;
1585                             angular.forEach($scope.statcats, function (s) {
1586
1587                                 if (!$scope.working)
1588                                     $scope.working = { statcats: {}, statcat_filter: undefined};
1589                                 if (!$scope.working.statcats)
1590                                     $scope.working.statcats = {};
1591
1592                                 if (!$scope.in_item_select) {
1593                                     $scope.working.statcats[s.id()] = undefined;
1594                                 }
1595                                 createStatcatUpdateWatcher(s.id());
1596                             });
1597                             $scope.in_item_select = false;
1598                             // do a refresh here to work around a race
1599                             // condition that can result in stat cats
1600                             // not being selected.
1601                             $scope.workingGridDataProvider.refresh();
1602                         });
1603                     }
1604                 }
1605             }
1606
1607             $scope.workingGridDataProvider.refresh();
1608         });
1609
1610         $scope.statcat_visible = function (sc_owner) {
1611             var visible = typeof $scope.working.statcat_filter === 'undefined' || !$scope.working.statcat_filter;
1612             angular.forEach(egCore.org.ancestors(sc_owner), function (ancestor_org) {
1613                 if ($scope.working.statcat_filter == ancestor_org.id())
1614                     visible = true;
1615             });
1616             return visible;
1617         }
1618
1619         $scope.suffix_list = [];
1620         itemSvc.get_suffixes(egCore.auth.user().ws_ou()).then(function(list){
1621             $scope.suffix_list = list;
1622         });
1623
1624         $scope.prefix_list = [];
1625         itemSvc.get_prefixes(egCore.auth.user().ws_ou()).then(function(list){
1626             $scope.prefix_list = list;
1627         });
1628
1629         $scope.classification_list = [];
1630         itemSvc.get_classifications().then(function(list){
1631             $scope.classification_list = list;
1632         });
1633
1634         $scope.$watch('completed_copies.length', function () {
1635             $scope.completedGridDataProvider.refresh();
1636         });
1637
1638         $scope.location_list = [];
1639         itemSvc.get_locations().then(function(list){
1640             $scope.location_list = list;
1641         });
1642         createSimpleUpdateWatcher('location');
1643
1644         $scope.status_list = [];
1645         itemSvc.get_magic_statuses().then(function(list){
1646             $scope.magic_status_list = list;
1647             createSimpleUpdateWatcher('status',$scope.magic_status_list);
1648         });
1649         itemSvc.get_statuses().then(function(list){
1650             $scope.status_list = list;
1651         });
1652
1653         $scope.circ_modifier_list = [];
1654         itemSvc.get_circ_mods().then(function(list){
1655             $scope.circ_modifier_list = list;
1656         });
1657         createSimpleUpdateWatcher('circ_modifier');
1658
1659         $scope.circ_type_list = [];
1660         itemSvc.get_circ_types().then(function(list){
1661             $scope.circ_type_list = list;
1662         });
1663         createSimpleUpdateWatcher('circ_as_type');
1664
1665         $scope.age_protect_list = [];
1666         itemSvc.get_age_protects().then(function(list){
1667             $scope.age_protect_list = list;
1668         });
1669         createSimpleUpdateWatcher('age_protect');
1670
1671         $scope.floating_list = [];
1672         itemSvc.get_floating_groups().then(function(list){
1673             $scope.floating_list = list;
1674         });
1675         createSimpleUpdateWatcher('floating');
1676
1677         createSimpleUpdateWatcher('circ_lib');
1678         createSimpleUpdateWatcher('circulate');
1679         createSimpleUpdateWatcher('holdable');
1680         createSimpleUpdateWatcher('fine_level');
1681         createSimpleUpdateWatcher('loan_duration');
1682         createSimpleUpdateWatcher('price');
1683         createSimpleUpdateWatcher('cost');
1684         createSimpleUpdateWatcher('deposit');
1685         createSimpleUpdateWatcher('deposit_amount');
1686         createSimpleUpdateWatcher('mint_condition');
1687         createSimpleUpdateWatcher('opac_visible');
1688         createSimpleUpdateWatcher('ref');
1689         createSimpleUpdateWatcher('alert_message');
1690
1691         $scope.saveCompletedCopies = function (and_exit) {
1692             var cnHash = {};
1693             var perCnCopies = {};
1694             angular.forEach( $scope.completed_copies, function (cp) {
1695                 var cn = cp.call_number();
1696                 var cn_cps = cp.call_number().copies();
1697                 cp.call_number().copies([]);
1698                 var cn_id = cp.call_number().id();
1699                 cp.call_number(cn_id); // prevent loops in JSON-ification
1700                 if (!cnHash[cn_id]) {
1701                     cnHash[cn_id] = egCore.idl.Clone(cn);
1702                     perCnCopies[cn_id] = [egCore.idl.Clone(cp)];
1703                 } else {
1704                     perCnCopies[cn_id].push(egCore.idl.Clone(cp));
1705                 }
1706                 cp.call_number(cn); // put the data back
1707                 cp.call_number().copies(cn_cps);
1708                 if (typeof cnHash[cn_id].prefix() == 'object')
1709                     cnHash[cn_id].prefix(cnHash[cn_id].prefix().id()); // un-object-ize some fields
1710                 if (typeof cnHash[cn_id].suffix() == 'object')
1711                     cnHash[cn_id].suffix(cnHash[cn_id].suffix().id()); // un-object-ize some fields
1712             });
1713
1714             angular.forEach(perCnCopies, function (v, k) {
1715                 cnHash[k].copies(v);
1716             });
1717
1718             cnList = [];
1719             angular.forEach(cnHash, function (v, k) {
1720                 cnList.push(v);
1721             });
1722
1723             egNet.request(
1724                 'open-ils.cat',
1725                 'open-ils.cat.asset.volume.fleshed.batch.update.override',
1726                 egCore.auth.token(), cnList, 1, { auto_merge_vols : 1, create_parts : 1, return_copy_ids : 1 }
1727             ).then(function(copy_ids) {
1728                 if (and_exit) {
1729                     $scope.dirty = false;
1730                     if ($scope.defaults.print_item_labels) {
1731                         egCore.net.request(
1732                             'open-ils.actor',
1733                             'open-ils.actor.anon_cache.set_value',
1734                             null, 'print-labels-these-copies', {
1735                                 copies : copy_ids
1736                             }
1737                         ).then(function(key) {
1738                             if (key) {
1739                                 var url = egCore.env.basePath + 'cat/printlabels/' + key;
1740                                 $timeout(function() { $window.open(url, '_blank') }).then(
1741                                     function() { $timeout(function(){$window.close()}); }
1742                                 );
1743                             } else {
1744                                 alert('Could not create anonymous cache key!');
1745                             }
1746                         });
1747                     } else {
1748                         $timeout(function(){$window.close()});
1749                     }
1750                 }
1751             });
1752         }
1753
1754         $scope.saveAndContinue = function () {
1755             $scope.saveCompletedCopies(false);
1756         }
1757
1758         $scope.workingSaveAndExit = function () {
1759             $scope.workingToComplete();
1760             $scope.saveAndExit();
1761         }
1762
1763         $scope.saveAndExit = function () {
1764             $scope.saveCompletedCopies(true);
1765         }
1766
1767     }
1768
1769     $scope.copy_notes_dialog = function(copy_list) {
1770         var default_pub = Boolean($scope.defaults.copy_notes_pub);
1771         if (!angular.isArray(copy_list)) copy_list = [copy_list];
1772
1773         return $uibModal.open({
1774             templateUrl: './cat/volcopy/t_copy_notes',
1775             backdrop: 'static',
1776             animation: true,
1777             controller:
1778                    ['$scope','$uibModalInstance',
1779             function($scope , $uibModalInstance) {
1780                 $scope.focusNote = true;
1781                 $scope.note = {
1782                     creator : egCore.auth.user().id(),
1783                     title   : '',
1784                     value   : '',
1785                     pub     : default_pub,
1786                 };
1787
1788                 $scope.require_initials = false;
1789                 egCore.org.settings([
1790                     'ui.staff.require_initials.copy_notes'
1791                 ]).then(function(set) {
1792                     $scope.require_initials = Boolean(set['ui.staff.require_initials.copy_notes']);
1793                 });
1794
1795                 $scope.note_list = [];
1796                 if (copy_list.length == 1) {
1797                     $scope.note_list = copy_list[0].notes();
1798                 }
1799
1800                 $scope.ok = function(note) {
1801
1802                     if ($scope.initials) {
1803                         note.value = egCore.strings.$replace(
1804                             egCore.strings.COPY_NOTE_INITIALS, {
1805                             value : note.value, 
1806                             initials : $scope.initials,
1807                             ws_ou : egCore.org.get(
1808                                 egCore.auth.user().ws_ou()).shortname()
1809                         });
1810                     }
1811
1812                     angular.forEach(copy_list, function (cp) {
1813                         if (!angular.isArray(cp.notes())) cp.notes([]);
1814                         var n = new egCore.idl.acpn();
1815                         n.isnew(1);
1816                         n.creator(note.creator);
1817                         n.pub(note.pub);
1818                         n.title(note.title);
1819                         n.value(note.value);
1820                         n.owning_copy(cp.id());
1821                         cp.notes().push( n );
1822                     });
1823
1824                     $uibModalInstance.close();
1825                 }
1826
1827                 $scope.cancel = function($event) {
1828                     $uibModalInstance.dismiss();
1829                     $event.preventDefault();
1830                 }
1831             }]
1832         });
1833     }
1834
1835     $scope.copy_tags_dialog = function(copy_list) {
1836         if (!angular.isArray(copy_list)) copy_list = [copy_list];
1837
1838         return $uibModal.open({
1839             templateUrl: './cat/volcopy/t_copy_tags',
1840             backdrop: 'static',
1841             animation: true,
1842             controller:
1843                    ['$scope','$uibModalInstance',
1844             function($scope , $uibModalInstance) {
1845
1846                 $scope.tag_map = [];
1847                 var tag_hash = {};
1848                 var shared_tags = {};
1849                 angular.forEach(copy_list, function (cp) {
1850                     angular.forEach(cp.tags(), function(tag) {
1851                         if (!(tag.tag().id() in shared_tags)) {
1852                             shared_tags[tag.tag().id()] = 1;
1853                         } else {
1854                             shared_tags[tag.tag().id()]++;
1855                         }
1856                         if (!(tag.tag().id() in tag_hash)) {
1857                             tag_hash[tag.tag().id()] = tag;
1858                         }
1859                     });
1860                 });
1861                 angular.forEach(tag_hash, function(value, key) {
1862                     if (shared_tags[key] == copy_list.length) {
1863                         $scope.tag_map.push(value);
1864                     }
1865                 });
1866
1867                 $scope.tag_types = [];
1868                 egCore.pcrud.retrieveAll('cctt', {order_by : { cctt : 'label' }}, {atomic : true}).then(function(list) {
1869                     $scope.tag_types = list;
1870                     $scope.tag_type = $scope.tag_types[0].code(); // just pick a default
1871                 });
1872
1873                 $scope.getTags = function(val) {
1874                     return egCore.pcrud.search('acpt',
1875                         { 
1876                             owner :  egCore.org.fullPath(egCore.auth.user().ws_ou(), true),
1877                             label : { 'startwith' : {
1878                                         transform: 'evergreen.lowercase',
1879                                         value : [ 'evergreen.lowercase', val ]
1880                                     }},
1881                             tag_type : $scope.tag_type
1882                         },
1883                         { order_by : { 'acpt' : ['label'] } }, { atomic: true }
1884                     ).then(function(list) {
1885                         return list.map(function(item) {
1886                             return item.label();
1887                         });
1888                     });
1889                 }
1890
1891                 $scope.addTag = function() {
1892                     var tagLabel = $scope.selectedLabel;
1893                     // clear the typeahead
1894                     $scope.selectedLabel = "";
1895
1896                     // first, check tags already associated with the copy
1897                     var foundMatch = false;
1898                     angular.forEach($scope.tag_map, function(tag) {
1899                         if (tag.tag().label() ==  tagLabel && tag.tag().tag_type() == $scope.tag_type) {
1900                             foundMatch = true;
1901                             if (tag.isdeleted()) tag.isdeleted(0); // just deleting the mapping
1902                         }
1903                     });
1904                     if (!foundMatch) {
1905                         egCore.pcrud.search('acpt',
1906                             { 
1907                                 owner : egCore.org.fullPath(egCore.auth.user().ws_ou(), true),
1908                                 label : tagLabel,
1909                                 tag_type : $scope.tag_type
1910                             },
1911                             { order_by : { 'acpt' : ['label'] } }, { atomic: true }
1912                         ).then(function(list) {
1913                             if (list.length > 0) {
1914                                 var newMap = new egCore.idl.acptcm();
1915                                 newMap.isnew(1);
1916                                 newMap.copy(copy_list[0].id());
1917                                 newMap.tag(egCore.idl.Clone(list[0]));
1918                                 $scope.tag_map.push(newMap);
1919                             } else {
1920                                 var newTag = new egCore.idl.acpt();
1921                                 newTag.isnew(1);
1922                                 newTag.owner(egCore.auth.user().ws_ou());
1923                                 newTag.label(tagLabel);
1924                                 newTag.pub('t');
1925                                 newTag.tag_type($scope.tag_type);
1926
1927                                 var newMap = new egCore.idl.acptcm();
1928                                 newMap.isnew(1);
1929                                 newMap.copy(copy_list[0].id());
1930                                 newMap.tag(newTag);
1931                                 $scope.tag_map.push(newMap);
1932                             }
1933                         });
1934                     }
1935                 }
1936
1937                 $scope.ok = function(note) {
1938                     // in the multi-item case, this works OK for
1939                     // adding new maps to existing tags, but doesn't handle
1940                     // all possibilities
1941                     angular.forEach(copy_list, function (cp) {
1942                         cp.tags($scope.tag_map);
1943                     });
1944                     $uibModalInstance.close();
1945                 }
1946
1947                 $scope.cancel = function($event) {
1948                     $uibModalInstance.dismiss();
1949                     $event.preventDefault();
1950                 }
1951             }]
1952         });
1953     }
1954
1955 }])
1956
1957 .directive("egVolTemplate", function () {
1958     return {
1959         restrict: 'E',
1960         replace: true,
1961         template: '<div ng-include="'+"'/eg/staff/cat/volcopy/t_attr_edit'"+'"></div>',
1962         scope: {
1963             editTemplates: '=',
1964         },
1965         controller : ['$scope','$window','itemSvc','egCore','ngToast',
1966             function ( $scope , $window , itemSvc , egCore , ngToast) {
1967
1968                 $scope.i18n = egCore.i18n;
1969
1970                 $scope.defaults = { // If defaults are not set at all, allow everything
1971                     barcode_checkdigit : false,
1972                     auto_gen_barcode : false,
1973                     statcats : true,
1974                     copy_notes : true,
1975                     copy_tags : true,
1976                     attributes : {
1977                         status : true,
1978                         loan_duration : true,
1979                         fine_level : true,
1980                         cost : true,
1981                         alerts : true,
1982                         deposit : true,
1983                         deposit_amount : true,
1984                         opac_visible : true,
1985                         price : true,
1986                         circulate : true,
1987                         mint_condition : true,
1988                         circ_lib : true,
1989                         ref : true,
1990                         circ_modifier : true,
1991                         circ_as_type : true,
1992                         location : true,
1993                         holdable : true,
1994                         age_protect : true,
1995                         floating : true
1996                     }
1997                 };
1998
1999                 $scope.fetchDefaults = function () {
2000                     egCore.hatch.getItem('cat.copy.defaults').then(function(t) {
2001                         if (t) {
2002                             $scope.defaults = t;
2003                             $scope.working.statcat_filter = $scope.defaults.statcat_filter;
2004                             if (
2005                                     typeof $scope.defaults.statcat_filter == 'object' &&
2006                                     Object.keys($scope.defaults.statcat_filter).length > 0
2007                                 ) {
2008                                 // want fieldmapper object here...
2009                                 $scope.defaults.statcat_filter =
2010                                     egCore.idl.Clone($scope.defaults.statcat_filter);
2011                                 // ... and ID here
2012                                 $scope.working.statcat_filter = $scope.defaults.statcat_filter.id();
2013                             }
2014                         }
2015                     });
2016                 }
2017                 $scope.fetchDefaults();
2018
2019                 $scope.dirty = false;
2020                 $scope.$watch('dirty',
2021                     function(newVal, oldVal) {
2022                         if (newVal && newVal != oldVal) {
2023                             $($window).on('beforeunload.template', function(){
2024                                 return 'There is unsaved template data!'
2025                             });
2026                         } else {
2027                             $($window).off('beforeunload.template');
2028                         }
2029                     }
2030                 );
2031
2032                 $scope.template_controls = true;
2033
2034                 $scope.fetchTemplates = function () {
2035                     itemSvc.get_acp_templates().then(function(t) {
2036                         if (t) {
2037                             $scope.templates = t;
2038                             $scope.template_name_list = Object.keys(t).sort();
2039                         }
2040                     });
2041                 }
2042                 $scope.fetchTemplates();
2043             
2044                 $scope.applyTemplate = function (n) {
2045                     angular.forEach($scope.templates[n], function (v,k) {
2046                         if (k == 'circ_lib') {
2047                             $scope.working[k] = egCore.org.get(v);
2048                         } else if (!angular.isObject(v)) {
2049                             $scope.working[k] = angular.copy(v);
2050                         } else {
2051                             angular.forEach(v, function (sv,sk) {
2052                                 if (!(k in $scope.working))
2053                                     $scope.working[k] = {};
2054                                 $scope.working[k][sk] = angular.copy(sv);
2055                             });
2056                         }
2057                     });
2058                     $scope.template_name = '';
2059                 }
2060
2061                 $scope.deleteTemplate = function (n) {
2062                     if (n) {
2063                         delete $scope.templates[n]
2064                         $scope.template_name_list = Object.keys($scope.templates).sort();
2065                         $scope.template_name = '';
2066                         itemSvc.save_acp_templates($scope.templates);
2067                         $scope.$parent.fetchTemplates();
2068                         ngToast.create(egCore.strings.VOL_COPY_TEMPLATE_SUCCESS_DELETE);
2069                     }
2070                 }
2071
2072                 $scope.saveTemplate = function (n) {
2073                     if (n) {
2074                         var tmpl = {};
2075             
2076                         angular.forEach($scope.working, function (v,k) {
2077                             if (angular.isObject(v)) { // we'll use the pkey
2078                                 if (v.id) v = v.id();
2079                                 else if (v.code) v = v.code();
2080                             }
2081             
2082                             tmpl[k] = v;
2083                         });
2084             
2085                         $scope.templates[n] = tmpl;
2086                         $scope.template_name_list = Object.keys($scope.templates).sort();
2087             
2088                         itemSvc.save_acp_templates($scope.templates);
2089                         $scope.$parent.fetchTemplates();
2090
2091                         $scope.dirty = false;
2092                     } else {
2093                         // save all templates, as we might do after an import
2094                         itemSvc.save_acp_templates($scope.templates);
2095                         $scope.$parent.fetchTemplates();
2096                     }
2097                     ngToast.create(egCore.strings.VOL_COPY_TEMPLATE_SUCCESS_SAVE);
2098                 }
2099             
2100                 $scope.templates = {};
2101                 $scope.imported_templates = { data : '' };
2102                 $scope.template_name = '';
2103                 $scope.template_name_list = [];
2104
2105                 $scope.$watch('imported_templates.data', function(newVal, oldVal) {
2106                     if (newVal && newVal != oldVal) {
2107                         try {
2108                             var newTemplates = JSON.parse(newVal);
2109                             if (!Object.keys(newTemplates).length) return;
2110                             angular.forEach(Object.keys(newTemplates), function (k) {
2111                                 $scope.templates[k] = newTemplates[k];
2112                             });
2113                             itemSvc.save_acp_templates($scope.templates);
2114                             $scope.fetchTemplates();
2115                         } catch (E) {
2116                             console.log('tried to import an invalid copy template file');
2117                         }
2118                     }
2119                 });
2120
2121                 $scope.tracker = function (x,f) { if (x) return x[f]() };
2122                 $scope.idTracker = function (x) { if (x) return $scope.tracker(x,'id') };
2123                 $scope.cant_have_vols = function (id) { return !egCore.org.CanHaveVolumes(id); };
2124             
2125                 $scope.orgById = function (id) { return egCore.org.get(id) }
2126                 $scope.statusById = function (id) {
2127                     return $scope.status_list.filter( function (s) { return s.id() == id } )[0];
2128                 }
2129                 $scope.locationById = function (id) {
2130                     return $scope.location_cache[''+id];
2131                 }
2132             
2133                 createSimpleUpdateWatcher = function (field) {
2134                     $scope.$watch('working.' + field, function () {
2135                         var newval = $scope.working[field];
2136             
2137                         if (typeof newval != 'undefined') {
2138                             $scope.dirty = true;
2139                             if (angular.isObject(newval)) { // we'll use the pkey
2140                                 if (newval.id) $scope.working[field] = newval.id();
2141                                 else if (newval.code) $scope.working[field] = newval.code();
2142                             }
2143             
2144                             if (""+newval == "" || newval == null) {
2145                                 $scope.working[field] = undefined;
2146                             }
2147             
2148                         }
2149                     });
2150                 }
2151             
2152                 $scope.working = {
2153                     statcats: {},
2154                     statcat_filter: undefined
2155                 };
2156             
2157                 $scope.statcat_visible = function (sc_owner) {
2158                     var visible = typeof $scope.working.statcat_filter === 'undefined' || !$scope.working.statcat_filter;
2159                     angular.forEach(egCore.org.ancestors(sc_owner), function (ancestor_org) {
2160                         if ($scope.working.statcat_filter == ancestor_org.id())
2161                             visible = true;
2162                     });
2163                     return visible;
2164                 }
2165
2166                 createStatcatUpdateWatcher = function (id) {
2167                     return $scope.$watch('working.statcats[' + id + ']', function () {
2168                         if ($scope.working.statcats) {
2169                             var newval = $scope.working.statcats[id];
2170                 
2171                             if (typeof newval != 'undefined') {
2172                                 $scope.dirty = true;
2173                                 if (angular.isObject(newval)) { // we'll use the pkey
2174                                     newval = newval.id();
2175                                 }
2176                 
2177                                 if (""+newval == "" || newval == null) {
2178                                     $scope.working.statcats[id] = undefined;
2179                                     newval = null;
2180                                 }
2181                 
2182                             }
2183                         }
2184                     });
2185                 }
2186
2187                 $scope.clearWorking = function () {
2188                     angular.forEach($scope.working, function (v,k,o) {
2189                         if (!angular.isObject(v)) {
2190                             if (typeof v != 'undefined')
2191                                 $scope.working[k] = undefined;
2192                         } else if (k != 'circ_lib') {
2193                             angular.forEach(v, function (sv,sk) {
2194                                 $scope.working[k][sk] = undefined;
2195                             });
2196                         }
2197                     });
2198                     $scope.working.circ_lib = undefined; // special
2199                     $scope.dirty = false;
2200                 }
2201
2202                 $scope.working = {};
2203                 $scope.location_orgs = [];
2204                 $scope.location_cache = {};
2205             
2206                 $scope.location_list = [];
2207                 itemSvc.get_locations(
2208                     egCore.org.fullPath( egCore.auth.user().ws_ou(), true )
2209                 ).then(function(list){
2210                     $scope.location_list = list;
2211                 });
2212                 createSimpleUpdateWatcher('location');
2213
2214                 $scope.statcat_filter_list = egCore.org.fullPath( egCore.auth.user().ws_ou() );
2215
2216                 $scope.statcats = [];
2217                 itemSvc.get_statcats(
2218                     egCore.org.fullPath( egCore.auth.user().ws_ou(), true )
2219                 ).then(function(list){
2220                     $scope.statcats = list;
2221                     angular.forEach($scope.statcats, function (s) {
2222
2223                         if (!$scope.working)
2224                             $scope.working = { statcats: {}, statcat_filter: undefined};
2225                         if (!$scope.working.statcats)
2226                             $scope.working.statcats = {};
2227
2228                         $scope.working.statcats[s.id()] = undefined;
2229                         createStatcatUpdateWatcher(s.id());
2230                     });
2231                 });
2232             
2233                 $scope.status_list = [];
2234                 itemSvc.get_magic_statuses().then(function(list){
2235                     $scope.magic_status_list = list;
2236                 });
2237                 itemSvc.get_statuses().then(function(list){
2238                     $scope.status_list = list;
2239                 });
2240                 createSimpleUpdateWatcher('status');
2241             
2242                 $scope.circ_modifier_list = [];
2243                 itemSvc.get_circ_mods().then(function(list){
2244                     $scope.circ_modifier_list = list;
2245                 });
2246                 createSimpleUpdateWatcher('circ_modifier');
2247             
2248                 $scope.circ_type_list = [];
2249                 itemSvc.get_circ_types().then(function(list){
2250                     $scope.circ_type_list = list;
2251                 });
2252                 createSimpleUpdateWatcher('circ_as_type');
2253             
2254                 $scope.age_protect_list = [];
2255                 itemSvc.get_age_protects().then(function(list){
2256                     $scope.age_protect_list = list;
2257                 });
2258                 createSimpleUpdateWatcher('age_protect');
2259             
2260                 createSimpleUpdateWatcher('circulate');
2261                 createSimpleUpdateWatcher('holdable');
2262                 createSimpleUpdateWatcher('fine_level');
2263                 createSimpleUpdateWatcher('loan_duration');
2264                 createSimpleUpdateWatcher('cost');
2265                 createSimpleUpdateWatcher('deposit');
2266                 createSimpleUpdateWatcher('deposit_amount');
2267                 createSimpleUpdateWatcher('mint_condition');
2268                 createSimpleUpdateWatcher('opac_visible');
2269                 createSimpleUpdateWatcher('ref');
2270                 createSimpleUpdateWatcher('alert_message');
2271
2272                 $scope.suffix_list = [];
2273                 itemSvc.get_suffixes(egCore.auth.user().ws_ou()).then(function(list){
2274                     $scope.suffix_list = list;
2275                 });
2276
2277                 $scope.prefix_list = [];
2278                 itemSvc.get_prefixes(egCore.auth.user().ws_ou()).then(function(list){
2279                     $scope.prefix_list = list;
2280                 });
2281
2282                 $scope.classification_list = [];
2283                 itemSvc.get_classifications().then(function(list){
2284                     $scope.classification_list = list;
2285                 });
2286
2287                 createSimpleUpdateWatcher('working.callnumber.classification');
2288                 createSimpleUpdateWatcher('working.callnumber.prefix');
2289                 createSimpleUpdateWatcher('working.callnumber.suffix');
2290             }
2291         ]
2292     }
2293 })
2294
2295