]> git.evergreen-ils.org Git - Evergreen.git/blob - Open-ILS/web/js/ui/default/staff/cat/volcopy/app.js
LP#1570091: webstaff: fix handling of default copy status
[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(function($routeProvider, $locationProvider, $compileProvider) {
15     $locationProvider.html5Mode(true);
16     $compileProvider.aHrefSanitizationWhitelist(/^\s*(https?|blob):/); // grid export
17
18     var resolver = {
19         delay : ['egStartup', function(egStartup) { return egStartup.go(); }]
20     };
21
22     $routeProvider.when('/cat/volcopy/:dataKey', {
23         templateUrl: './cat/volcopy/t_view',
24         controller: 'EditCtrl',
25         resolve : resolver
26     });
27
28     $routeProvider.when('/cat/volcopy/:dataKey/:mode', {
29         templateUrl: './cat/volcopy/t_view',
30         controller: 'EditCtrl',
31         resolve : resolver
32     });
33 })
34
35 .factory('itemSvc', 
36        ['egCore','$q',
37 function(egCore , $q) {
38
39     var service = {
40         currently_generating : false,
41         auto_gen_barcode : false,
42         barcode_checkdigit : false,
43         new_cp_id : 0,
44         new_cn_id : 0,
45         tree : {}, // holds lib->cn->copy hash stack
46         copies : [] // raw copy list
47     };
48
49     service.nextBarcode = function(bc) {
50         service.currently_generating = true;
51         return egCore.net.request(
52             'open-ils.cat',
53             'open-ils.cat.item.barcode.autogen',
54             egCore.auth.token(),
55             bc, 1, { checkdigit: service.barcode_checkdigit }
56         ).then(function(resp) { // get_barcodes
57             var evt = egCore.evt.parse(resp);
58             if (!evt) return resp[0];
59             return '';
60         });
61     };
62
63     service.checkBarcode = function(bc) {
64         if (!service.barcode_checkdigit) return true;
65         if (bc != Number(bc)) return false;
66         bc = bc.toString();
67         // "16.00" == Number("16.00"), but the . is bad.
68         // Throw out any barcode that isn't just digits
69         if (bc.search(/\D/) != -1) return false;
70         var last_digit = bc.substr(bc.length-1);
71         var stripped_barcode = bc.substr(0,bc.length-1);
72         return service.barcodeCheckdigit(stripped_barcode).toString() == last_digit;
73     };
74
75     service.barcodeCheckdigit = function(bc) {
76         var reverse_barcode = bc.toString().split('').reverse();
77         var check_sum = 0; var multiplier = 2;
78         for (var i = 0; i < reverse_barcode.length; i++) {
79             var digit = reverse_barcode[i];
80             var product = digit * multiplier; product = product.toString();
81             var temp_sum = 0;
82             for (var j = 0; j < product.length; j++) {
83                 temp_sum += Number( product[j] );
84             }
85             check_sum += Number( temp_sum );
86             multiplier = ( multiplier == 2 ? 1 : 2 );
87         }
88         check_sum = check_sum.toString();
89         var next_multiple_of_10 = (check_sum.match(/(\d*)\d$/)[1] * 10) + 10;
90         var check_digit = next_multiple_of_10 - Number(check_sum); if (check_digit == 10) check_digit = 0;
91         return check_digit;
92     };
93
94     // returns a promise resolved with the list of circ mods
95     service.get_classifications = function() {
96         if (egCore.env.acnc)
97             return $q.when(egCore.env.acnc.list);
98
99         return egCore.pcrud.retrieveAll('acnc', null, {atomic : true})
100         .then(function(list) {
101             egCore.env.absorbList(list, 'acnc');
102             return list;
103         });
104     };
105
106     service.get_prefixes = function(org) {
107         return egCore.pcrud.search('acnp',
108             {owning_lib : egCore.org.fullPath(org, true)},
109             {order_by : { acnp : 'label_sortkey' }}, {atomic : true}
110         );
111
112     };
113
114     service.get_statcats = function(orgs) {
115         return egCore.pcrud.search('asc',
116             {owner : orgs},
117             { flesh : 1,
118               flesh_fields : {
119                 asc : ['owner','entries']
120               }
121             },
122             { atomic : true }
123         );
124     };
125
126     service.get_locations = function(orgs) {
127         return egCore.pcrud.search('acpl',
128             {owning_lib : orgs},
129             {order_by : { acpl : 'name' }}, {atomic : true}
130         );
131     };
132
133     service.get_suffixes = function(org) {
134         return egCore.pcrud.search('acns',
135             {owning_lib : egCore.org.fullPath(org, true)},
136             {order_by : { acns : 'label_sortkey' }}, {atomic : true}
137         );
138
139     };
140
141     service.get_statuses = function() {
142         if (egCore.env.ccs)
143             return $q.when(egCore.env.ccs.list);
144
145         return egCore.pcrud.retrieveAll('ccs', {order_by : { ccs : 'name' }}, {atomic : true}).then(
146             function(list) {
147                 egCore.env.absorbList(list, 'ccs');
148                 return list;
149             }
150         );
151
152     };
153
154     service.get_circ_mods = function() {
155         if (egCore.env.ccm)
156             return $q.when(egCore.env.ccm.list);
157
158         return egCore.pcrud.retrieveAll('ccm', {}, {atomic : true}).then(
159             function(list) {
160                 egCore.env.absorbList(list, 'ccm');
161                 return list;
162             }
163         );
164
165     };
166
167     service.get_circ_types = function() {
168         if (egCore.env.citm)
169             return $q.when(egCore.env.citm.list);
170
171         return egCore.pcrud.retrieveAll('citm', {}, {atomic : true}).then(
172             function(list) {
173                 egCore.env.absorbList(list, 'citm');
174                 return list;
175             }
176         );
177
178     };
179
180     service.get_age_protects = function() {
181         if (egCore.env.crahp)
182             return $q.when(egCore.env.crahp.list);
183
184         return egCore.pcrud.retrieveAll('crahp', {}, {atomic : true}).then(
185             function(list) {
186                 egCore.env.absorbList(list, 'crahp');
187                 return list;
188             }
189         );
190
191     };
192
193     service.get_floating_groups = function() {
194         if (egCore.env.cfg)
195             return $q.when(egCore.env.cfg.list);
196
197         return egCore.pcrud.retrieveAll('cfg', {}, {atomic : true}).then(
198             function(list) {
199                 egCore.env.absorbList(list, 'cfg');
200                 return list;
201             }
202         );
203
204     };
205
206     service.bmp_parts = {};
207     service.get_parts = function(rec) {
208         if (service.bmp_parts[rec])
209             return $q.when(service.bmp_parts[rec]);
210
211         return egCore.pcrud.search('bmp',
212             {record : rec, deleted : 'f'},
213             null, {atomic : true}
214         ).then(function(list) {
215             service.bmp_parts[rec] = list;
216             return list;
217         });
218
219     };
220
221     service.flesh = {   
222         flesh : 3, 
223         flesh_fields : {
224             acp : ['call_number','parts','stat_cat_entries', 'notes'],
225             acn : ['label_class','prefix','suffix']
226         }
227     }
228
229     service.addCopy = function (cp) {
230
231         if (!cp.parts()) cp.parts([]); // just in case...
232
233         var lib = cp.call_number().owning_lib();
234         var cn = cp.call_number().id();
235
236         if (!service.tree[lib]) service.tree[lib] = {};
237         if (!service.tree[lib][cn]) service.tree[lib][cn] = [];
238
239         service.tree[lib][cn].push(cp);
240         service.copies.push(cp);
241     }
242
243     service.fetchIds = function(idList) {
244         service.tree = {}; // clear the tree on fetch
245         service.copies = []; // clear the copy list on fetch
246         return egCore.pcrud.search('acp', { 'id' : idList }, service.flesh).then(null,null,
247             function(copy) {
248                 service.addCopy(copy);
249             }
250         );
251     }
252
253     // create a new acp object with default values
254     // (both hard-coded and coming from OU settings)
255     service.generateNewCopy = function(callNumber, owningLib, isFastAdd, isNew) {
256         var cp = new egCore.idl.acp();
257         cp.id( --service.new_cp_id );
258         if (isNew) {
259             cp.isnew( true );
260         }
261         cp.circ_lib( owningLib );
262         cp.call_number( callNumber );
263         cp.deposit(0);
264         cp.price(0);
265         cp.deposit_amount(0);
266         cp.fine_level(2); // Normal
267         cp.loan_duration(2); // Normal
268         cp.location(1); // Stacks
269         cp.circulate('t');
270         cp.holdable('t');
271         cp.opac_visible('t');
272         cp.ref('f');
273         cp.mint_condition('t');
274
275         var status_setting = isFastAdd ?
276             'cat.default_copy_status_fast' :
277             'cat.default_copy_status_normal';
278         egCore.org.settings(
279             [status_setting],
280             owningLib
281         ).then(function(set) {
282             var default_ccs = parseInt(set[status_setting]);
283             if (isNaN(default_ccs))
284                 default_ccs = (isFastAdd ? 0 : 5); // 0 is Available, 5 is In Process
285             cp.status(default_ccs);
286         });
287
288         return cp;
289     }
290
291     return service;
292 }])
293
294 .directive("egVolCopyEdit", function () {
295     return {
296         restrict: 'E',
297         replace: true,
298         template:
299             '<div class="row">'+
300                 '<div class="col-xs-5" ng-class="{'+"'has-error'"+':barcode_has_error}">'+
301                     '<input id="{{callNumber.id()}}_{{copy.id()}}"'+
302                     ' eg-enter="nextBarcode(copy.id())" class="form-control"'+
303                     ' type="text" ng-model="barcode" ng-change="updateBarcode()"/>'+
304                 '</div>'+
305                 '<div class="col-xs-3"><input class="form-control" type="number" ng-model="copy_number" ng-change="updateCopyNo()"/></div>'+
306                 '<div class="col-xs-4"><eg-basic-combo-box eg-disabled="record == 0" list="parts" selected="part"></eg-basic-combo-box></div>'+
307             '</div>',
308
309         scope: { focusNext: "=", copy: "=", callNumber: "=", index: "@", record: "@" },
310         controller : ['$scope','itemSvc','egCore',
311             function ( $scope , itemSvc , egCore ) {
312                 $scope.new_part_id = 0;
313                 $scope.barcode_has_error = false;
314
315                 $scope.nextBarcode = function (i) {
316                     $scope.focusNext(i, $scope.barcode);
317                 }
318
319                 $scope.updateBarcode = function () {
320                     if ($scope.barcode != '')
321                         $scope.barcode_has_error = !Boolean(itemSvc.checkBarcode($scope.barcode));
322                     $scope.copy.barcode($scope.barcode);
323                     $scope.copy.ischanged(1);
324                     if (itemSvc.currently_generating)
325                         $scope.focusNext($scope.copy.id(), $scope.barcode);
326                 };
327
328                 $scope.updateCopyNo = function () { $scope.copy.copy_number($scope.copy_number); $scope.copy.ischanged(1); };
329                 $scope.updatePart = function () {
330                     if ($scope.part) {
331                         var p = $scope.part_list.filter(function (x) {
332                             return x.label() == $scope.part
333                         });
334                         if (p.length > 0) { // preexisting part
335                             $scope.copy.parts(p)
336                         } else { // create one...
337                             var part = new egCore.idl.bmp();
338                             part.id( --$scope.new_part_id );
339                             part.isnew( true );
340                             part.label( $scope.part );
341                             part.record( $scope.callNumber.record() );
342                             $scope.copy.parts([part]);
343                             $scope.copy.ischanged(1);
344                         }
345                     } else {
346                         $scope.copy.parts([]);
347                     }
348                     $scope.copy.ischanged(1);
349                 }
350                 $scope.$watch('part', $scope.updatePart);
351
352                 $scope.barcode = $scope.copy.barcode();
353                 $scope.copy_number = $scope.copy.copy_number();
354
355                 if ($scope.copy.parts()) {
356                     $scope.part = $scope.copy.parts()[0];
357                     if ($scope.part) $scope.part = $scope.part.label();
358                 };
359
360                 $scope.parts = [];
361                 $scope.part_list = [];
362
363                 itemSvc.get_parts($scope.callNumber.record()).then(function(list){
364                     $scope.part_list = list;
365                     angular.forEach(list, function(p){ $scope.parts.push(p.label()) });
366                     $scope.parts = angular.copy($scope.parts);
367                 });
368
369             }
370         ]
371
372     }
373 })
374
375 .directive("egVolRow", function () {
376     return {
377         restrict: 'E',
378         replace: true,
379         transclude: true,
380         template:
381             '<div class="row">'+
382                 '<div class="col-xs-2">'+
383                     '<select ng-disabled="record == 0" class="form-control" ng-model="classification" ng-change="updateClassification()" ng-options="cl.name() for cl in classification_list"/>'+
384                 '</div>'+
385                 '<div class="col-xs-1">'+
386                     '<select ng-disabled="record == 0" class="form-control" ng-model="prefix" ng-change="updatePrefix()" ng-options="p.label() for p in prefix_list"/>'+
387                 '</div>'+
388                 '<div class="col-xs-2"><input ng-disabled="record == 0" class="form-control" type="text" ng-change="updateLabel()" ng-model="label"/></div>'+
389                 '<div class="col-xs-1">'+
390                     '<select ng-disabled="record == 0" class="form-control" ng-model="suffix" ng-change="updateSuffix()" ng-options="s.label() for s in suffix_list"/>'+
391                 '</div>'+
392                 '<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>'+
393                 '<div ng-hide="onlyVols" class="col-xs-5">'+
394                     '<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>'+
395                 '</div>'+
396             '</div>',
397
398         scope: {focusNext: "=", allcopies: "=", copies: "=", onlyVols: "=", record: "@" },
399         controller : ['$scope','itemSvc','egCore',
400             function ( $scope , itemSvc , egCore ) {
401                 $scope.callNumber =  $scope.copies[0].call_number();
402
403                 $scope.idTracker = function (x) { if (x && x.id) return x.id() };
404
405                 // XXX $() is not working! arg
406                 $scope.focusNextBarcode = function (i, prev_bc) {
407                     var n;
408                     var yep = false;
409                     angular.forEach($scope.copies, function (cp) {
410                         if (n) return;
411
412                         if (cp.id() == i) {
413                             yep = true;
414                             return;
415                         }
416
417                         if (yep) n = cp.id();
418                     });
419
420                     if (n) {
421                         var next = '#' + $scope.callNumber.id() + '_' + n;
422                         var el = $(next);
423                         if (el) {
424                             if (!itemSvc.currently_generating) el.focus();
425                             if (prev_bc && itemSvc.auto_gen_barcode && el.val() == "") {
426                                 itemSvc.nextBarcode(prev_bc).then(function(bc){
427                                     el.focus();
428                                     el.val(bc);
429                                     el.trigger('change');
430                                 });
431                             } else {
432                                 itemSvc.currently_generating = false;
433                             }
434                         }
435                     } else {
436                         $scope.focusNext($scope.callNumber.id(),prev_bc)
437                     }
438                 }
439
440                 $scope.suffix_list = [];
441                 itemSvc.get_suffixes($scope.callNumber.owning_lib()).then(function(list){
442                     $scope.suffix_list = list;
443                     $scope.$watch('callNumber.suffix()', function (v) {
444                         if (angular.isObject(v)) v = v.id();
445                         $scope.suffix = $scope.suffix_list.filter( function (s) {
446                             return s.id() == v;
447                         })[0];
448                     });
449
450                 });
451                 $scope.updateSuffix = function () {
452                     angular.forEach($scope.copies, function(cp) {
453                         cp.call_number().suffix($scope.suffix);
454                         cp.call_number().ischanged(1);
455                     });
456                 }
457
458                 $scope.prefix_list = [];
459                 itemSvc.get_prefixes($scope.callNumber.owning_lib()).then(function(list){
460                     $scope.prefix_list = list;
461                     $scope.$watch('callNumber.prefix()', function (v) {
462                         if (angular.isObject(v)) v = v.id();
463                         $scope.prefix = $scope.prefix_list.filter(function (p) {
464                             return p.id() == v;
465                         })[0];
466                     });
467
468                 });
469                 $scope.updatePrefix = function () {
470                     angular.forEach($scope.copies, function(cp) {
471                         cp.call_number().prefix($scope.prefix);
472                         cp.call_number().ischanged(1);
473                     });
474                 }
475                 $scope.$watch('callNumber.owning_lib()', function(oldLib, newLib) {
476                     if (oldLib == newLib) return;
477                     var currentPrefix = $scope.callNumber.prefix();
478                     if (angular.isObject(currentPrefix)) currentPrefix = currentPrefix.id();
479                     itemSvc.get_prefixes($scope.callNumber.owning_lib()).then(function(list){
480                         $scope.prefix_list = list;
481                         var newPrefixId = $scope.prefix_list.filter(function (p) {
482                             return p.id() == currentPrefix;
483                         })[0] || -1;
484                         if (newPrefixId.id) newPrefixId = newPrefixId.id();
485                         $scope.prefix = $scope.prefix_list.filter(function (p) {
486                             return p.id() == newPrefixId;
487                         })[0];
488                         if ($scope.newPrefixId != currentPrefix) {
489                             $scope.callNumber.prefix($scope.prefix);
490                         }
491                     });
492                     var currentSuffix = $scope.callNumber.suffix();
493                     if (angular.isObject(currentSuffix)) currentSuffix = currentSuffix.id();
494                     itemSvc.get_suffixes($scope.callNumber.owning_lib()).then(function(list){
495                         $scope.suffix_list = list;
496                         var newSuffixId = $scope.suffix_list.filter(function (s) {
497                             return s.id() == currentSuffix;
498                         })[0] || -1;
499                         if (newSuffixId.id) newSuffixId = newSuffixId.id();
500                         $scope.suffix = $scope.suffix_list.filter(function (s) {
501                             return s.id() == newSuffixId;
502                         })[0];
503                         if ($scope.newSuffixId != currentSuffix) {
504                             $scope.callNumber.suffix($scope.suffix);
505                         }
506                     });
507                 });
508
509                 $scope.classification_list = [];
510                 itemSvc.get_classifications().then(function(list){
511                     $scope.classification_list = list;
512                     $scope.$watch('callNumber.label_class()', function (v) {
513                         if (angular.isObject(v)) v = v.id();
514                         $scope.classification = $scope.classification_list.filter(function (c) {
515                             return c.id() == v;
516                         })[0];
517                     });
518
519                 });
520                 $scope.updateClassification = function () {
521                     angular.forEach($scope.copies, function(cp) {
522                         cp.call_number().label_class($scope.classification);
523                         cp.call_number().ischanged(1);
524                     });
525                 }
526
527                 $scope.updateLabel = function () {
528                     angular.forEach($scope.copies, function(cp) {
529                         cp.call_number().label($scope.label);
530                         cp.call_number().ischanged(1);
531                     });
532                 }
533
534                 $scope.$watch('callNumber.label()', function (v) {
535                     $scope.label = v;
536                 });
537
538                 $scope.prefix = $scope.callNumber.prefix();
539                 $scope.suffix = $scope.callNumber.suffix();
540                 $scope.classification = $scope.callNumber.label_class();
541                 $scope.label = $scope.callNumber.label();
542
543                 $scope.copy_count = $scope.copies.length;
544                 $scope.orig_copy_count = $scope.copy_count;
545
546                 $scope.changeCPCount = function () {
547                     while ($scope.copy_count > $scope.copies.length) {
548                         var cp = itemSvc.generateNewCopy(
549                             $scope.callNumber,
550                             $scope.callNumber.owning_lib(),
551                             $scope.fast_add,
552                             true
553                         );
554                         $scope.copies.push( cp );
555                         $scope.allcopies.push( cp );
556
557                     }
558
559                     if ($scope.copy_count >= $scope.orig_copy_count) {
560                         var how_many = $scope.copies.length - $scope.copy_count;
561                         if (how_many > 0) {
562                             var dead = $scope.copies.splice($scope.copy_count,how_many);
563                             $scope.callNumber.copies($scope.copies);
564
565                             // Trimming the global list is a bit more tricky
566                             angular.forEach( dead, function (d) {
567                                 angular.forEach( $scope.allcopies, function (l, i) { 
568                                     if (l === d) $scope.allcopies.splice(i,1);
569                                 });
570                             });
571                         }
572                     }
573                 }
574
575             }
576         ]
577
578     }
579 })
580
581 .directive("egVolEdit", function () {
582     return {
583         restrict: 'E',
584         replace: true,
585         template:
586             '<div class="row">'+
587                 '<div class="col-xs-1"><eg-org-selector alldisabled="{{record == 0}}" selected="owning_lib" disable-test="cant_have_vols"></eg-org-selector></div>'+
588                 '<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>'+
589                 '<div class="col-xs-10">'+
590                     '<eg-vol-row only-vols="onlyVols" record="{{record}}"'+
591                         'ng-repeat="(cn,copies) in struct | orderBy:cn track by cn" '+
592                         'focus-next="focusNextFirst" copies="copies" allcopies="allcopies">'+
593                     '</eg-vol-row>'+
594                 '</div>'+
595             '</div>',
596
597         scope: { focusNext: "=", allcopies: "=", struct: "=", lib: "@", record: "@", onlyVols: "=" },
598         controller : ['$scope','itemSvc','egCore',
599             function ( $scope , itemSvc , egCore ) {
600                 $scope.first_cn = Object.keys($scope.struct)[0];
601                 $scope.full_cn = $scope.struct[$scope.first_cn][0].call_number();
602
603                 $scope.defaults = {};
604                 egCore.hatch.getItem('cat.copy.defaults').then(function(t) {
605                     if (t) {
606                         $scope.defaults = t;
607                     }
608                 });
609
610                 $scope.focusNextFirst = function(prev_cn,prev_bc) {
611                     var n;
612                     var yep = false;
613                     angular.forEach(Object.keys($scope.struct).sort(), function (cn) {
614                         if (n) return;
615
616                         if (cn == prev_cn) {
617                             yep = true;
618                             return;
619                         }
620
621                         if (yep) n = cn;
622                     });
623
624                     if (n) {
625                         var next = '#' + n + '_' + $scope.struct[n][0].id();
626                         var el = $(next);
627                         if (el) {
628                             if (!itemSvc.currently_generating) el.focus();
629                             if (prev_bc && itemSvc.auto_gen_barcode && el.val() == "") {
630                                 itemSvc.nextBarcode(prev_bc).then(function(bc){
631                                     el.focus();
632                                     el.val(bc);
633                                     el.trigger('change');
634                                 });
635                             } else {
636                                 itemSvc.currently_generating = false;
637                             }
638                         }
639                     } else {
640                         $scope.focusNext($scope.lib, prev_bc);
641                     }
642                 }
643
644                 $scope.cn_count = Object.keys($scope.struct).length;
645                 $scope.orig_cn_count = $scope.cn_count;
646
647                 $scope.owning_lib = egCore.org.get($scope.lib);
648                 $scope.$watch('owning_lib', function (oldLib, newLib) {
649                     if (oldLib == newLib) return;
650                     angular.forEach( Object.keys($scope.struct), function (cn) {
651                         $scope.struct[cn][0].call_number().owning_lib( $scope.owning_lib.id() );
652                         $scope.struct[cn][0].call_number().ischanged(1);
653                     });
654                 });
655
656                 $scope.cant_have_vols = function (id) { return !egCore.org.CanHaveVolumes(id); };
657
658                 $scope.$watch('cn_count', function (n) {
659                     var o = Object.keys($scope.struct).length;
660                     if (n > o) { // adding
661                         for (var i = o; o < n; o++) {
662                             var cn = new egCore.idl.acn();
663                             cn.id( --itemSvc.new_cn_id );
664                             cn.isnew( true );
665                             cn.prefix( $scope.defaults.prefix || -1 );
666                             cn.suffix( $scope.defaults.suffix || -1 );
667                             cn.label_class( $scope.defaults.classification || 1 );
668                             cn.owning_lib( $scope.owning_lib.id() );
669                             cn.record( $scope.full_cn.record() );
670
671                             var cp = itemSvc.generateNewCopy(
672                                 cn,
673                                 $scope.owning_lib.id(),
674                                 $scope.fast_add,
675                                 true
676                             );
677
678                             $scope.struct[cn.id()] = [cp];
679                             $scope.allcopies.push(cp);
680                             if (!scope.defaults.classification) {
681                                 egCore.org.settings(
682                                     ['cat.default_classification_scheme'],
683                                     cn.owning_lib()
684                                 ).then(function (val) {
685                                     cn.label_class(val['cat.default_classification_scheme']);
686                                 });
687                             }
688                         }
689                     } else if (n < o && n >= $scope.orig_cn_count) { // removing
690                         var how_many = o - n;
691                         var list = Object
692                                 .keys($scope.struct)
693                                 .sort(function(a, b){return parseInt(a)-parseInt(b)})
694                                 .filter(function(x){ return parseInt(x) <= 0 });
695                         for (var i = 0; i < how_many; i++) {
696                             // Trimming the global list is a bit more tricky
697                             angular.forEach($scope.struct[list[i]], function (d) {
698                                 angular.forEach( $scope.allcopies, function (l, j) { 
699                                     if (l === d) $scope.allcopies.splice(j,1);
700                                 });
701                             });
702                             delete $scope.struct[list[i]];
703                         }
704                     }
705                 });
706             }
707         ]
708
709     }
710 })
711
712 /**
713  * Edit controller!
714  */
715 .controller('EditCtrl', 
716        ['$scope','$q','$window','$routeParams','$location','$timeout','egCore','egNet','egGridDataProvider','itemSvc','$modal',
717 function($scope , $q , $window , $routeParams , $location , $timeout , egCore , egNet , egGridDataProvider , itemSvc , $modal) {
718
719     $scope.defaults = { // If defaults are not set at all, allow everything
720         barcode_checkdigit : false,
721         auto_gen_barcode : false,
722         statcats : true,
723         copy_notes : true,
724         attributes : {
725             status : true,
726             loan_duration : true,
727             fine_level : true,
728             cost : true,
729             alerts : true,
730             deposit : true,
731             deposit_amount : true,
732             opac_visible : true,
733             price : true,
734             circulate : true,
735             mint_condition : true,
736             circ_lib : true,
737             ref : true,
738             circ_modifier : true,
739             circ_as_type : true,
740             location : true,
741             holdable : true,
742             age_protect : true,
743             floating : true
744         }
745     };
746
747     $scope.embedded = ($routeParams.mode && $routeParams.mode == 'embedded') ? true : false;
748
749     $scope.saveDefaults = function () {
750         egCore.hatch.setItem('cat.copy.defaults', $scope.defaults);
751     }
752
753     $scope.fetchDefaults = function () {
754         egCore.hatch.getItem('cat.copy.defaults').then(function(t) {
755             if (t) {
756                 $scope.defaults = t;
757                 if (!$scope.batch) $scope.batch = {};
758                 $scope.batch.classification = $scope.defaults.classification;
759                 $scope.batch.prefix = $scope.defaults.prefix;
760                 $scope.batch.suffix = $scope.defaults.suffix;
761                 $scope.working.statcat_filter = $scope.defaults.statcat_filter;
762                 if (
763                         typeof $scope.defaults.statcat_filter == 'object' &&
764                         Object.keys($scope.defaults.statcat_filter).length > 0
765                    ) {
766                     // want fieldmapper object here...
767                     $scope.defaults.statcat_filter =
768                          egCore.idl.Clone($scope.defaults.statcat_filter);
769                     // ... and ID here
770                     $scope.working.statcat_filter = $scope.defaults.statcat_filter.id();
771                 }
772                 if ($scope.defaults.always_volumes) $scope.show_vols = true;
773                 if ($scope.defaults.barcode_checkdigit) itemSvc.barcode_checkdigit = true;
774                 if ($scope.defaults.auto_gen_barcode) itemSvc.auto_gen_barcode = true;
775             }
776         });
777     }
778     $scope.fetchDefaults();
779
780     $scope.$watch('defaults.statcat_filter', function() {
781         $scope.saveDefaults();
782     });
783     $scope.$watch('defaults.auto_gen_barcode', function (n,o) {
784         itemSvc.auto_gen_barcode = n
785     });
786
787     $scope.$watch('defaults.barcode_checkdigit', function (n,o) {
788         itemSvc.barcode_checkdigit = n
789     });
790
791     $scope.dirty = false;
792     $scope.$watch('dirty',
793         function(newVal, oldVal) {
794             if (newVal && newVal != oldVal) {
795                 $($window).on('beforeunload.edit', function(){
796                     return 'There is unsaved data!'
797                 });
798             } else {
799                 $($window).off('beforeunload.edit');
800             }
801         }
802     );
803
804     $scope.only_vols = false;
805     $scope.show_vols = true;
806     $scope.show_copies = true;
807
808     $scope.tracker = function (x,f) { if (x) return x[f]() };
809     $scope.idTracker = function (x) { if (x) return $scope.tracker(x,'id') };
810     $scope.cant_have_vols = function (id) { return !egCore.org.CanHaveVolumes(id); };
811
812     $scope.orgById = function (id) { return egCore.org.get(id) }
813     $scope.statusById = function (id) {
814         return $scope.status_list.filter( function (s) { return s.id() == id } )[0];
815     }
816     $scope.locationById = function (id) {
817         return $scope.location_cache[''+id];
818     }
819
820     $scope.workingToComplete = function () {
821         angular.forEach( $scope.workingGridControls.selectedItems(), function (c) {
822             angular.forEach( itemSvc.copies, function (w, i) {
823                 if (c === w)
824                     $scope.completed_copies = $scope.completed_copies.concat(itemSvc.copies.splice(i,1));
825             });
826         });
827
828         return true;
829     }
830
831     $scope.completeToWorking = function () {
832         angular.forEach( $scope.completedGridControls.selectedItems(), function (c) {
833             angular.forEach( $scope.completed_copies, function (w, i) {
834                 if (c === w)
835                     itemSvc.copies = itemSvc.copies.concat($scope.completed_copies.splice(i,1));
836             });
837         });
838
839         return true;
840     }
841
842     createSimpleUpdateWatcher = function (field) {
843         return $scope.$watch('working.' + field, function () {
844             var newval = $scope.working[field];
845
846             if (typeof newval != 'undefined') {
847                 if (angular.isObject(newval)) { // we'll use the pkey
848                     if (newval.id) newval = newval.id();
849                     else if (newval.code) newval = newval.code();
850                 }
851
852                 if (""+newval == "" || newval == null) {
853                     $scope.working[field] = undefined;
854                     newval = null;
855                 }
856
857                 if ($scope.workingGridControls && $scope.workingGridControls.selectedItems) {
858                     angular.forEach(
859                         $scope.workingGridControls.selectedItems(),
860                         function (cp) {
861                             if (cp[field]() !== newval) {
862                                 cp[field](newval);
863                                 cp.ischanged(1);
864                                 $scope.dirty = true;
865                             }
866                         }
867                     );
868                 }
869             }
870         });
871     }
872
873     $scope.working = {
874         statcats: {},
875         statcat_filter: undefined
876     };
877
878     $scope.statcatUpdate = function (id) {
879         var newval = $scope.working.statcats[id];
880
881         if (typeof newval != 'undefined') {
882             if (angular.isObject(newval)) { // we'll use the pkey
883                 newval = newval.id();
884             }
885     
886             if (""+newval == "" || newval == null) {
887                 $scope.working.statcats[id] = undefined;
888                 newval = null;
889             }
890     
891             if (!$scope.in_item_select && $scope.workingGridControls && $scope.workingGridControls.selectedItems) {
892                 angular.forEach(
893                     $scope.workingGridControls.selectedItems(),
894                     function (cp) {
895                         $scope.dirty = true;
896
897                         cp.stat_cat_entries(
898                             angular.forEach( cp.stat_cat_entries(), function (e) {
899                                 if (e.stat_cat() == id) { // mark deleted
900                                     e.isdeleted(1);
901                                 }
902                             })
903                         );
904     
905                         if (newval) {
906                             var e = new egCore.idl.asce();
907                             e.isnew( 1 );
908                             e.stat_cat( id );
909                             e.id(newval);
910
911                             cp.stat_cat_entries(
912                                 cp.stat_cat_entries() ?
913                                     cp.stat_cat_entries().concat([ e ]) :
914                                     [ e ]
915                             );
916
917                         }
918
919                         // trim out all deleted ones; the API used to
920                         // do the update doesn't actually consult
921                         // isdeleted for stat cat entries
922                         cp.stat_cat_entries(
923                             cp.stat_cat_entries().filter(function (e) {
924                                 return !Boolean(e.isdeleted());
925                             })
926                         );
927    
928                         cp.ischanged(1);
929                     }
930                 );
931             }
932         }
933     }
934
935     var dataKey = $routeParams.dataKey;
936     console.debug('dataKey: ' + dataKey);
937
938     if (dataKey && dataKey.length > 0) {
939
940         $scope.templates = {};
941         $scope.template_name = '';
942         $scope.template_name_list = [];
943
944         $scope.fetchTemplates = function () {
945             egCore.hatch.getItem('cat.copy.templates').then(function(t) {
946                 if (t) {
947                     $scope.templates = t;
948                     $scope.template_name_list = Object.keys(t);
949                 }
950             });
951             egCore.hatch.getItem('cat.copy.last_template').then(function(t) {
952                 if (t) $scope.template_name = t;
953             });
954         }
955         $scope.fetchTemplates();
956
957         $scope.applyTemplate = function (n) {
958             angular.forEach($scope.templates[n], function (v,k) {
959                 if (k == 'circ_lib') {
960                     $scope.working[k] = egCore.org.get(v);
961                 } else if (!angular.isObject(v)) {
962                     $scope.working[k] = angular.copy(v);
963                 } else {
964                     angular.forEach(v, function (sv,sk) {
965                         if (k == 'callnumber') {
966                             angular.forEach(v, function (cnv,cnk) {
967                                 $scope.batch[cnk] = cnv;
968                             });
969                             $scope.applyBatchCNValues();
970                         } else {
971                             $scope.working[k][sk] = angular.copy(sv);
972                             if (k == 'statcats') $scope.statcatUpdate(sk);
973                         }
974                     });
975                 }
976             });
977             egCore.hatch.setItem('cat.copy.last_template', n);
978         }
979
980         $scope.copytab = 'working';
981         $scope.tab = 'edit';
982         $scope.summaryRecord = null;
983         $scope.record_id = null;
984         $scope.data = {};
985         $scope.completed_copies = [];
986         $scope.location_orgs = [];
987         $scope.location_cache = {};
988         $scope.statcats = [];
989         if (!$scope.batch) $scope.batch = {};
990
991         $scope.applyBatchCNValues = function () {
992             if ($scope.data.tree) {
993                 angular.forEach($scope.data.tree, function(cn_hash) {
994                     angular.forEach(cn_hash, function(copies) {
995                         angular.forEach(copies, function(cp) {
996                             if (typeof $scope.batch.classification != 'undefined' && $scope.batch.classification != '') {
997                                 var label_class = $scope.classification_list.filter(function(p){ return p.id() == $scope.batch.classification })[0];
998                                 cp.call_number().label_class(label_class);
999                                 cp.call_number().ischanged(1);
1000                                 $scope.dirty = true;
1001                             }
1002                             if (typeof $scope.batch.prefix != 'undefined' && $scope.batch.prefix != '') {
1003                                 var prefix = $scope.prefix_list.filter(function(p){ return p.id() == $scope.batch.prefix })[0];
1004                                 cp.call_number().prefix(prefix);
1005                                 cp.call_number().ischanged(1);
1006                                 $scope.dirty = true;
1007                             }
1008                             if (typeof $scope.batch.label != 'undefined' && $scope.batch.label != '') {
1009                                 cp.call_number().label($scope.batch.label);
1010                                 cp.call_number().ischanged(1);
1011                                 $scope.dirty = true;
1012                             }
1013                             if (typeof $scope.batch.suffix != 'undefined' && $scope.batch.suffix != '') {
1014                                 var suffix = $scope.suffix_list.filter(function(p){ return p.id() == $scope.batch.suffix })[0];
1015                                 cp.call_number().suffix(suffix);
1016                                 cp.call_number().ischanged(1);
1017                                 $scope.dirty = true;
1018                             }
1019                         });
1020                     });
1021                 });
1022             }
1023         }
1024
1025         $scope.clearWorking = function () {
1026             angular.forEach($scope.working, function (v,k,o) {
1027                 if (!angular.isObject(v)) {
1028                     if (typeof v != 'undefined')
1029                         $scope.working[k] = undefined;
1030                 } else if (k != 'circ_lib') {
1031                     angular.forEach(v, function (sv,sk) {
1032                         if (typeof v != 'undefined')
1033                             $scope.working[k][sk] = undefined;
1034                     });
1035                 }
1036             });
1037             $scope.working.circ_lib = undefined; // special
1038         }
1039
1040         $scope.completedGridDataProvider = egGridDataProvider.instance({
1041             get : function(offset, count) {
1042                 //return provider.arrayNotifier(itemSvc.copies, offset, count);
1043                 return this.arrayNotifier($scope.completed_copies, offset, count);
1044             }
1045         });
1046
1047         $scope.completedGridControls = {};
1048
1049         $scope.workingGridDataProvider = egGridDataProvider.instance({
1050             get : function(offset, count) {
1051                 //return provider.arrayNotifier(itemSvc.copies, offset, count);
1052                 return this.arrayNotifier(itemSvc.copies, offset, count);
1053             }
1054         });
1055
1056         $scope.workingGridControls = {};
1057         $scope.add_vols_copies = false;
1058         $scope.is_fast_add = false;
1059
1060         egNet.request(
1061             'open-ils.actor',
1062             'open-ils.actor.anon_cache.get_value',
1063             dataKey, 'edit-these-copies'
1064         ).then(function (data) {
1065
1066             if (data) {
1067                 if (data.hide_vols && !$scope.defaults.always_volumes) $scope.show_vols = false;
1068                 if (data.hide_copies) {
1069                     $scope.show_copies = false;
1070                     $scope.only_vols = true;
1071                 }
1072
1073                 $scope.record_id = data.record_id;
1074
1075                 function fetchRaw () {
1076                     if (!$scope.only_vols) $scope.dirty = true;
1077                     $scope.add_vols_copies = true;
1078
1079                     /* data.raw data structure looks like this:
1080                      * [{
1081                      *      callnumber : $cn_id, // optional, to add a copy to a cn
1082                      *      owner      : $org, // optional, defaults to ws_ou
1083                      *      label      : $cn_label, // optional, to supply a label on a new cn
1084                      *      barcode    : $cp_barcode // optional, to supply a barcode on a new cp
1085                      *      fast_add   : boolean // optional, to specify whether this came
1086                      *                              in as a fast add
1087                      * },...]
1088                      * 
1089                      * All can be left out and a completely empty vol/copy combo will be vivicated.
1090                      */
1091
1092                     angular.forEach(
1093                         data.raw,
1094                         function (proto) {
1095                             if (proto.fast_add) $scope.is_fast_add = true;
1096                             if (proto.callnumber) {
1097                                 return egCore.pcrud.retrieve('acn', proto.callnumber)
1098                                 .then(function(cn) {
1099                                     var cp = new itemSvc.generateNewCopy(
1100                                         cn,
1101                                         proto.owner || egCore.auth.user().ws_ou(),
1102                                         $scope.is_fast_add,
1103                                         ((!$scope.only_vols) ? true : false)
1104                                     );
1105
1106                                     if (proto.barcode) cp.barcode( proto.barcode );
1107
1108                                     itemSvc.addCopy(cp)
1109                                 });
1110                             } else {
1111                                 var cn = new egCore.idl.acn();
1112                                 cn.id( --itemSvc.new_cn_id );
1113                                 cn.isnew( true );
1114                                 cn.prefix( $scope.defaults.prefix || -1 );
1115                                 cn.suffix( $scope.defaults.suffix || -1 );
1116                                 cn.owning_lib( proto.owner || egCore.auth.user().ws_ou() );
1117                                 cn.record( $scope.record_id );
1118                                 egCore.org.settings(
1119                                     ['cat.default_classification_scheme'],
1120                                     cn.owning_lib()
1121                                 ).then(function (val) {
1122                                     cn.label_class(
1123                                         $scope.defaults.classification ||
1124                                         val['cat.default_classification_scheme'] ||
1125                                         1
1126                                     );
1127                                     if (proto.label) {
1128                                         cn.label( proto.label );
1129                                     } else {
1130                                         egCore.net.request(
1131                                             'open-ils.cat',
1132                                             'open-ils.cat.biblio.record.marc_cn.retrieve',
1133                                             $scope.record_id,
1134                                             cn.label_class()
1135                                         ).then(function(cn_array) {
1136                                             if (cn_array.length > 0) {
1137                                                 for (var field in cn_array[0]) {
1138                                                     cn.label( cn_array[0][field] );
1139                                                     break;
1140                                                 }
1141                                             }
1142                                         });
1143                                     }
1144                                 });
1145
1146                                 var cp = new itemSvc.generateNewCopy(
1147                                     cn,
1148                                     proto.owner || egCore.auth.user().ws_ou(),
1149                                     $scope.is_fast_add,
1150                                     true
1151                                 );
1152
1153                                 if (proto.barcode) cp.barcode( proto.barcode );
1154
1155                                 itemSvc.addCopy(cp)
1156                             }
1157     
1158                         }
1159                     );
1160
1161                     return itemSvc.copies;
1162                 }
1163
1164                 if (data.copies && data.copies.length)
1165                     return itemSvc.fetchIds(data.copies).then(fetchRaw);
1166
1167                 return fetchRaw();
1168
1169             }
1170
1171         }).then( function() {
1172             $scope.data = itemSvc;
1173             $scope.workingGridDataProvider.refresh();
1174         });
1175
1176         $scope.focusNextFirst = function(prev_lib,prev_bc) {
1177             var n;
1178             var yep = false;
1179             angular.forEach(Object.keys($scope.data.tree).sort(), function (lib) {
1180                 if (n) return;
1181
1182                 if (lib == prev_lib) {
1183                     yep = true;
1184                     return;
1185                 }
1186
1187                 if (yep) n = lib;
1188             });
1189
1190             if (n) {
1191                 var first_cn = Object.keys($scope.data.tree[n])[0];
1192                 var next = '#' + first_cn + '_' + $scope.data.tree[n][first_cn][0].id();
1193                 var el = $(next);
1194                 if (el) {
1195                     if (!itemSvc.currently_generating) el.focus();
1196                     if (prev_bc && itemSvc.auto_gen_barcode && el.val() == "") {
1197                         itemSvc.nextBarcode(prev_bc).then(function(bc){
1198                             el.focus();
1199                             el.val(bc);
1200                             el.trigger('change');
1201                         });
1202                     } else {
1203                         itemSvc.currently_generating = false;
1204                     }
1205                 }
1206             }
1207         }
1208
1209         $scope.in_item_select = false;
1210         $scope.afterItemSelect = function() { $scope.in_item_select = false };
1211         $scope.handleItemSelect = function (item_list) {
1212             if (item_list && item_list.length > 0) {
1213                 $scope.in_item_select = true;
1214
1215                 angular.forEach(Object.keys($scope.defaults.attributes), function (attr) {
1216
1217                     var value_hash = {};
1218                     angular.forEach(item_list, function (item) {
1219                         if (item[attr]) {
1220                             var v = item[attr]()
1221                             if (angular.isObject(v)) {
1222                                 if (v.id) v = v.id();
1223                                 else if (v.code) v = v.code();
1224                             }
1225                             value_hash[v] = 1;
1226                         }
1227                     });
1228
1229                     if (Object.keys(value_hash).length == 1) {
1230                         if (attr == 'circ_lib') {
1231                             $scope.working[attr] = egCore.org.get(item_list[0][attr]());
1232                         } else {
1233                             $scope.working[attr] = item_list[0][attr]();
1234                         }
1235                     } else {
1236                         $scope.working[attr] = undefined;
1237                     }
1238                 });
1239
1240                 angular.forEach($scope.statcats, function (sc) {
1241
1242                     var counter = -1;
1243                     var value_hash = {};
1244                     var none = false;
1245                     angular.forEach(item_list, function (item) {
1246                         if (item.stat_cat_entries()) {
1247                             if (item.stat_cat_entries().length > 0) {
1248                                 var right_sc = item.stat_cat_entries().filter(function (e) {
1249                                     return e.stat_cat() == sc.id() && !Boolean(e.isdeleted());
1250                                 });
1251
1252                                 if (right_sc.length > 0) {
1253                                     value_hash[right_sc[0].id()] = right_sc[0].id();
1254                                 } else {
1255                                     none = true;
1256                                 }
1257                             }
1258                         } else {
1259                             none = true;
1260                         }
1261                     });
1262
1263                     if (!none && Object.keys(value_hash).length == 1) {
1264                         $scope.working.statcats[sc.id()] = value_hash[Object.keys(value_hash)[0]];
1265                     } else {
1266                         $scope.working.statcats[sc.id()] = undefined;
1267                     }
1268                 });
1269
1270             } else {
1271                 $scope.clearWorking();
1272             }
1273
1274         }
1275
1276         $scope.$watch('data.copies.length', function () {
1277             if ($scope.data.copies) {
1278                 var base_orgs = $scope.data.copies.map(function(cp){
1279                     return cp.circ_lib()
1280                 }).concat(
1281                     $scope.data.copies.map(function(cp){
1282                         return cp.call_number().owning_lib()
1283                     })
1284                 ).concat(
1285                     [egCore.auth.user().ws_ou()]
1286                 ).filter(function(e,i,a){
1287                     return a.lastIndexOf(e) === i;
1288                 });
1289
1290                 var all_orgs = [];
1291                 angular.forEach(base_orgs, function(o) {
1292                     all_orgs = all_orgs.concat( egCore.org.fullPath(o, true) );
1293                 });
1294
1295                 var final_orgs = all_orgs.filter(function(e,i,a){
1296                     return a.lastIndexOf(e) === i;
1297                 }).sort(function(a, b){return parseInt(a)-parseInt(b)});
1298
1299                 if ($scope.location_orgs.toString() != final_orgs.toString()) {
1300                     $scope.location_orgs = final_orgs;
1301                     if ($scope.location_orgs.length) {
1302                         itemSvc.get_locations($scope.location_orgs).then(function(list){
1303                             angular.forEach(list, function(l) {
1304                                 $scope.location_cache[ ''+l.id() ] = l;
1305                             });
1306                             $scope.location_list = list;
1307                         });
1308
1309                         $scope.statcat_filter_list = [];
1310                         angular.forEach($scope.location_orgs, function (o) {
1311                             $scope.statcat_filter_list.push(egCore.org.get(o));
1312                         });
1313
1314                         itemSvc.get_statcats($scope.location_orgs).then(function(list){
1315                             $scope.statcats = list;
1316                             angular.forEach($scope.statcats, function (s) {
1317
1318                                 if (!$scope.working)
1319                                     $scope.working = { statcats: {}, statcat_filter: undefined};
1320                                 if (!$scope.working.statcats)
1321                                     $scope.working.statcats = {};
1322
1323                                 if (!$scope.in_item_select) {
1324                                     $scope.working.statcats[s.id()] = undefined;
1325                                 }
1326                                 createStatcatUpdateWatcher(s.id());
1327                             });
1328                             $scope.in_item_select = false;
1329                             // do a refresh here to work around a race
1330                             // condition that can result in stat cats
1331                             // not being selected.
1332                             $scope.workingGridDataProvider.refresh();
1333                         });
1334                     }
1335                 }
1336             }
1337
1338             $scope.workingGridDataProvider.refresh();
1339         });
1340
1341         $scope.statcat_visible = function (sc_owner) {
1342             var visible = typeof $scope.working.statcat_filter === 'undefined' || !$scope.working.statcat_filter;
1343             angular.forEach(egCore.org.ancestors(sc_owner), function (ancestor_org) {
1344                 if ($scope.working.statcat_filter == ancestor_org.id())
1345                     visible = true;
1346             });
1347             return visible;
1348         }
1349
1350         $scope.suffix_list = [];
1351         itemSvc.get_suffixes(egCore.auth.user().ws_ou()).then(function(list){
1352             $scope.suffix_list = list;
1353         });
1354
1355         $scope.prefix_list = [];
1356         itemSvc.get_prefixes(egCore.auth.user().ws_ou()).then(function(list){
1357             $scope.prefix_list = list;
1358         });
1359
1360         $scope.classification_list = [];
1361         itemSvc.get_classifications().then(function(list){
1362             $scope.classification_list = list;
1363         });
1364
1365         $scope.$watch('completed_copies.length', function () {
1366             $scope.completedGridDataProvider.refresh();
1367         });
1368
1369         $scope.location_list = [];
1370         itemSvc.get_locations().then(function(list){
1371             $scope.location_list = list;
1372         });
1373         createSimpleUpdateWatcher('location');
1374
1375         $scope.status_list = [];
1376         itemSvc.get_statuses().then(function(list){
1377             $scope.status_list = list;
1378         });
1379         createSimpleUpdateWatcher('status');
1380
1381         $scope.circ_modifier_list = [];
1382         itemSvc.get_circ_mods().then(function(list){
1383             $scope.circ_modifier_list = list;
1384         });
1385         createSimpleUpdateWatcher('circ_modifier');
1386
1387         $scope.circ_type_list = [];
1388         itemSvc.get_circ_types().then(function(list){
1389             $scope.circ_type_list = list;
1390         });
1391         createSimpleUpdateWatcher('circ_as_type');
1392
1393         $scope.age_protect_list = [];
1394         itemSvc.get_age_protects().then(function(list){
1395             $scope.age_protect_list = list;
1396         });
1397         createSimpleUpdateWatcher('age_protect');
1398
1399         $scope.floating_list = [];
1400         itemSvc.get_floating_groups().then(function(list){
1401             $scope.floating_list = list;
1402         });
1403         createSimpleUpdateWatcher('floating');
1404
1405         createSimpleUpdateWatcher('circ_lib');
1406         createSimpleUpdateWatcher('circulate');
1407         createSimpleUpdateWatcher('holdable');
1408         createSimpleUpdateWatcher('fine_level');
1409         createSimpleUpdateWatcher('loan_duration');
1410         createSimpleUpdateWatcher('price');
1411         createSimpleUpdateWatcher('cost');
1412         createSimpleUpdateWatcher('deposit');
1413         createSimpleUpdateWatcher('deposit_amount');
1414         createSimpleUpdateWatcher('mint_condition');
1415         createSimpleUpdateWatcher('opac_visible');
1416         createSimpleUpdateWatcher('ref');
1417
1418         $scope.saveCompletedCopies = function (and_exit) {
1419             var cnHash = {};
1420             var perCnCopies = {};
1421             angular.forEach( $scope.completed_copies, function (cp) {
1422                 var cn = cp.call_number();
1423                 var cn_cps = cp.call_number().copies();
1424                 cp.call_number().copies([]);
1425                 var cn_id = cp.call_number().id();
1426                 cp.call_number(cn_id); // prevent loops in JSON-ification
1427                 if (!cnHash[cn_id]) {
1428                     cnHash[cn_id] = egCore.idl.Clone(cn);
1429                     perCnCopies[cn_id] = [egCore.idl.Clone(cp)];
1430                 } else {
1431                     perCnCopies[cn_id].push(egCore.idl.Clone(cp));
1432                 }
1433                 cp.call_number(cn); // put the data back
1434                 cp.call_number().copies(cn_cps);
1435                 if (typeof cnHash[cn_id].prefix() == 'object')
1436                     cnHash[cn_id].prefix(cnHash[cn_id].prefix().id()); // un-object-ize some fields
1437                 if (typeof cnHash[cn_id].suffix() == 'object')
1438                     cnHash[cn_id].suffix(cnHash[cn_id].suffix().id()); // un-object-ize some fields
1439             });
1440
1441             angular.forEach(perCnCopies, function (v, k) {
1442                 cnHash[k].copies(v);
1443             });
1444
1445             cnList = [];
1446             angular.forEach(cnHash, function (v, k) {
1447                 cnList.push(v);
1448             });
1449
1450             egNet.request(
1451                 'open-ils.cat',
1452                 'open-ils.cat.asset.volume.fleshed.batch.update.override',
1453                 egCore.auth.token(), cnList, 1, { auto_merge_vols : 1, create_parts : 1 }
1454             ).then(function(update_count) {
1455                 if (and_exit) {
1456                     $scope.dirty = false;
1457                     $timeout(function(){$window.close()});
1458                 }
1459             });
1460         }
1461
1462         $scope.saveAndContinue = function () {
1463             $scope.saveCompletedCopies(false);
1464         }
1465
1466         $scope.workingSaveAndExit = function () {
1467             $scope.workingToComplete();
1468             $scope.saveAndExit();
1469         }
1470
1471         $scope.saveAndExit = function () {
1472             $scope.saveCompletedCopies(true);
1473         }
1474
1475     }
1476
1477     $scope.copy_notes_dialog = function(copy_list) {
1478         var default_pub = Boolean($scope.defaults.copy_notes_pub);
1479         if (!angular.isArray(copy_list)) copy_list = [copy_list];
1480
1481         return $modal.open({
1482             templateUrl: './cat/volcopy/t_copy_notes',
1483             animation: true,
1484             controller:
1485                    ['$scope','$modalInstance',
1486             function($scope , $modalInstance) {
1487                 $scope.focusNote = true;
1488                 $scope.note = {
1489                     creator : egCore.auth.user().id(),
1490                     title   : '',
1491                     value   : '',
1492                     pub     : default_pub,
1493                 };
1494
1495                 $scope.require_initials = false;
1496                 egCore.org.settings([
1497                     'ui.staff.require_initials.copy_notes'
1498                 ]).then(function(set) {
1499                     $scope.require_initials = Boolean(set['ui.staff.require_initials.copy_notes']);
1500                 });
1501
1502                 $scope.note_list = [];
1503                 if (copy_list.length == 1) {
1504                     $scope.note_list = copy_list[0].notes();
1505                 }
1506
1507                 $scope.ok = function(note) {
1508
1509                     if (note.initials) note.value += ' [' + note.initials + ']';
1510                     angular.forEach(copy_list, function (cp) {
1511                         if (!angular.isArray(cp.notes())) cp.notes([]);
1512                         var n = new egCore.idl.acpn();
1513                         n.isnew(1);
1514                         n.creator(note.creator);
1515                         n.pub(note.pub);
1516                         n.title(note.title);
1517                         n.value(note.value);
1518                         n.owning_copy(cp.id());
1519                         cp.notes().push( n );
1520                     });
1521
1522                     $modalInstance.close();
1523                 }
1524
1525                 $scope.cancel = function($event) {
1526                     $modalInstance.dismiss();
1527                     $event.preventDefault();
1528                 }
1529             }]
1530         });
1531     }
1532
1533 }])
1534
1535 .directive("egVolTemplate", function () {
1536     return {
1537         restrict: 'E',
1538         replace: true,
1539         template: '<div ng-include="'+"'/eg/staff/cat/volcopy/t_attr_edit'"+'"></div>',
1540         scope: { },
1541         controller : ['$scope','$window','itemSvc','egCore',
1542             function ( $scope , $window , itemSvc , egCore ) {
1543
1544                 $scope.defaults = { // If defaults are not set at all, allow everything
1545                     barcode_checkdigit : false,
1546                     auto_gen_barcode : false,
1547                     statcats : true,
1548                     copy_notes : true,
1549                     attributes : {
1550                         status : true,
1551                         loan_duration : true,
1552                         fine_level : true,
1553                         cost : true,
1554                         alerts : true,
1555                         deposit : true,
1556                         deposit_amount : true,
1557                         opac_visible : true,
1558                         price : true,
1559                         circulate : true,
1560                         mint_condition : true,
1561                         circ_lib : true,
1562                         ref : true,
1563                         circ_modifier : true,
1564                         circ_as_type : true,
1565                         location : true,
1566                         holdable : true,
1567                         age_protect : true,
1568                         floating : true
1569                     }
1570                 };
1571
1572                 $scope.fetchDefaults = function () {
1573                     egCore.hatch.getItem('cat.copy.defaults').then(function(t) {
1574                         if (t) {
1575                             $scope.defaults = t;
1576                             $scope.working.statcat_filter = $scope.defaults.statcat_filter;
1577                             if (
1578                                     typeof $scope.defaults.statcat_filter == 'object' &&
1579                                     Object.keys($scope.defaults.statcat_filter).length > 0
1580                                 ) {
1581                                 // want fieldmapper object here...
1582                                 $scope.defaults.statcat_filter =
1583                                     egCore.idl.Clone($scope.defaults.statcat_filter);
1584                                 // ... and ID here
1585                                 $scope.working.statcat_filter = $scope.defaults.statcat_filter.id();
1586                             }
1587                         }
1588                     });
1589                 }
1590                 $scope.fetchDefaults();
1591
1592                 $scope.dirty = false;
1593                 $scope.$watch('dirty',
1594                     function(newVal, oldVal) {
1595                         if (newVal && newVal != oldVal) {
1596                             $($window).on('beforeunload.template', function(){
1597                                 return 'There is unsaved template data!'
1598                             });
1599                         } else {
1600                             $($window).off('beforeunload.template');
1601                         }
1602                     }
1603                 );
1604
1605                 $scope.template_controls = true;
1606
1607                 $scope.fetchTemplates = function () {
1608                     egCore.hatch.getItem('cat.copy.templates').then(function(t) {
1609                         if (t) {
1610                             $scope.templates = t;
1611                             $scope.template_name_list = Object.keys(t);
1612                         }
1613                     });
1614                 }
1615                 $scope.fetchTemplates();
1616             
1617                 $scope.applyTemplate = function (n) {
1618                     angular.forEach($scope.templates[n], function (v,k) {
1619                         if (k == 'circ_lib') {
1620                             $scope.working[k] = egCore.org.get(v);
1621                         } else if (!angular.isObject(v)) {
1622                             $scope.working[k] = angular.copy(v);
1623                         } else {
1624                             angular.forEach(v, function (sv,sk) {
1625                                 if (!(k in $scope.working))
1626                                     $scope.working[k] = {};
1627                                 $scope.working[k][sk] = angular.copy(sv);
1628                             });
1629                         }
1630                     });
1631                     $scope.template_name = '';
1632                 }
1633
1634                 $scope.deleteTemplate = function (n) {
1635                     if (n) {
1636                         delete $scope.templates[n]
1637                         $scope.template_name_list = Object.keys($scope.templates);
1638                         $scope.template_name = '';
1639                         egCore.hatch.setItem('cat.copy.templates', $scope.templates);
1640                         $scope.$parent.fetchTemplates();
1641                     }
1642                 }
1643
1644                 $scope.saveTemplate = function (n) {
1645                     if (n) {
1646                         var tmpl = {};
1647             
1648                         angular.forEach($scope.working, function (v,k) {
1649                             if (angular.isObject(v)) { // we'll use the pkey
1650                                 if (v.id) v = v.id();
1651                                 else if (v.code) v = v.code();
1652                             }
1653             
1654                             tmpl[k] = v;
1655                         });
1656             
1657                         $scope.templates[n] = tmpl;
1658                         $scope.template_name_list = Object.keys($scope.templates);
1659             
1660                         egCore.hatch.setItem('cat.copy.templates', $scope.templates);
1661                         $scope.$parent.fetchTemplates();
1662
1663                         $scope.dirty = false;
1664                     } else {
1665                         // save all templates, as we might do after an import
1666                         egCore.hatch.setItem('cat.copy.templates', $scope.templates);
1667                         $scope.$parent.fetchTemplates();
1668                     }
1669                 }
1670             
1671                 $scope.templates = {};
1672                 $scope.imported_templates = { data : '' };
1673                 $scope.template_name = '';
1674                 $scope.template_name_list = [];
1675
1676                 $scope.$watch('imported_templates.data', function(newVal, oldVal) {
1677                     if (newVal && newVal != oldVal) {
1678                         try {
1679                             var newTemplates = JSON.parse(newVal);
1680                             if (!Object.keys(newTemplates).length) return;
1681                             $scope.templates = newTemplates;
1682                             $scope.template_name_list = Object.keys(newTemplates);
1683                             $scope.template_name = '';
1684                         } catch (E) {
1685                             console.log('tried to import an invalid copy template file');
1686                         }
1687                     }
1688                 });
1689
1690                 $scope.tracker = function (x,f) { if (x) return x[f]() };
1691                 $scope.idTracker = function (x) { if (x) return $scope.tracker(x,'id') };
1692                 $scope.cant_have_vols = function (id) { return !egCore.org.CanHaveVolumes(id); };
1693             
1694                 $scope.orgById = function (id) { return egCore.org.get(id) }
1695                 $scope.statusById = function (id) {
1696                     return $scope.status_list.filter( function (s) { return s.id() == id } )[0];
1697                 }
1698                 $scope.locationById = function (id) {
1699                     return $scope.location_cache[''+id];
1700                 }
1701             
1702                 createSimpleUpdateWatcher = function (field) {
1703                     $scope.$watch('working.' + field, function () {
1704                         var newval = $scope.working[field];
1705             
1706                         if (typeof newval != 'undefined') {
1707                             $scope.dirty = true;
1708                             if (angular.isObject(newval)) { // we'll use the pkey
1709                                 if (newval.id) $scope.working[field] = newval.id();
1710                                 else if (newval.code) $scope.working[field] = newval.code();
1711                             }
1712             
1713                             if (""+newval == "" || newval == null) {
1714                                 $scope.working[field] = undefined;
1715                             }
1716             
1717                         }
1718                     });
1719                 }
1720             
1721                 $scope.working = {
1722                     statcats: {},
1723                     statcat_filter: undefined
1724                 };
1725             
1726                 $scope.statcat_visible = function (sc_owner) {
1727                     var visible = typeof $scope.working.statcat_filter === 'undefined' || !$scope.working.statcat_filter;
1728                     angular.forEach(egCore.org.ancestors(sc_owner), function (ancestor_org) {
1729                         if ($scope.working.statcat_filter == ancestor_org.id())
1730                             visible = true;
1731                     });
1732                     return visible;
1733                 }
1734
1735                 createStatcatUpdateWatcher = function (id) {
1736                     return $scope.$watch('working.statcats[' + id + ']', function () {
1737                         if ($scope.working.statcats) {
1738                             var newval = $scope.working.statcats[id];
1739                 
1740                             if (typeof newval != 'undefined') {
1741                                 $scope.dirty = true;
1742                                 if (angular.isObject(newval)) { // we'll use the pkey
1743                                     newval = newval.id();
1744                                 }
1745                 
1746                                 if (""+newval == "" || newval == null) {
1747                                     $scope.working.statcats[id] = undefined;
1748                                     newval = null;
1749                                 }
1750                 
1751                             }
1752                         }
1753                     });
1754                 }
1755
1756                 $scope.clearWorking = function () {
1757                     angular.forEach($scope.working, function (v,k,o) {
1758                         if (!angular.isObject(v)) {
1759                             if (typeof v != 'undefined')
1760                                 $scope.working[k] = undefined;
1761                         } else if (k != 'circ_lib') {
1762                             angular.forEach(v, function (sv,sk) {
1763                                 $scope.working[k][sk] = undefined;
1764                             });
1765                         }
1766                     });
1767                     $scope.working.circ_lib = undefined; // special
1768                     $scope.dirty = false;
1769                 }
1770
1771                 $scope.working = {};
1772                 $scope.location_orgs = [];
1773                 $scope.location_cache = {};
1774             
1775                 $scope.location_list = [];
1776                 itemSvc.get_locations(
1777                     egCore.org.fullPath( egCore.auth.user().ws_ou(), true )
1778                 ).then(function(list){
1779                     $scope.location_list = list;
1780                 });
1781                 createSimpleUpdateWatcher('location');
1782
1783                 $scope.statcat_filter_list = egCore.org.fullPath( egCore.auth.user().ws_ou() );
1784
1785                 $scope.statcats = [];
1786                 itemSvc.get_statcats(
1787                     egCore.org.fullPath( egCore.auth.user().ws_ou(), true )
1788                 ).then(function(list){
1789                     $scope.statcats = list;
1790                     angular.forEach($scope.statcats, function (s) {
1791
1792                         if (!$scope.working)
1793                             $scope.working = { statcats: {}, statcat_filter: undefined};
1794                         if (!$scope.working.statcats)
1795                             $scope.working.statcats = {};
1796
1797                         $scope.working.statcats[s.id()] = undefined;
1798                         createStatcatUpdateWatcher(s.id());
1799                     });
1800                 });
1801             
1802                 $scope.status_list = [];
1803                 itemSvc.get_statuses().then(function(list){
1804                     $scope.status_list = list;
1805                 });
1806                 createSimpleUpdateWatcher('status');
1807             
1808                 $scope.circ_modifier_list = [];
1809                 itemSvc.get_circ_mods().then(function(list){
1810                     $scope.circ_modifier_list = list;
1811                 });
1812                 createSimpleUpdateWatcher('circ_modifier');
1813             
1814                 $scope.circ_type_list = [];
1815                 itemSvc.get_circ_types().then(function(list){
1816                     $scope.circ_type_list = list;
1817                 });
1818                 createSimpleUpdateWatcher('circ_as_type');
1819             
1820                 $scope.age_protect_list = [];
1821                 itemSvc.get_age_protects().then(function(list){
1822                     $scope.age_protect_list = list;
1823                 });
1824                 createSimpleUpdateWatcher('age_protect');
1825             
1826                 createSimpleUpdateWatcher('circulate');
1827                 createSimpleUpdateWatcher('holdable');
1828                 createSimpleUpdateWatcher('fine_level');
1829                 createSimpleUpdateWatcher('loan_duration');
1830                 createSimpleUpdateWatcher('cost');
1831                 createSimpleUpdateWatcher('deposit');
1832                 createSimpleUpdateWatcher('deposit_amount');
1833                 createSimpleUpdateWatcher('mint_condition');
1834                 createSimpleUpdateWatcher('opac_visible');
1835                 createSimpleUpdateWatcher('ref');
1836
1837                 $scope.suffix_list = [];
1838                 itemSvc.get_suffixes(egCore.auth.user().ws_ou()).then(function(list){
1839                     $scope.suffix_list = list;
1840                 });
1841
1842                 $scope.prefix_list = [];
1843                 itemSvc.get_prefixes(egCore.auth.user().ws_ou()).then(function(list){
1844                     $scope.prefix_list = list;
1845                 });
1846
1847                 $scope.classification_list = [];
1848                 itemSvc.get_classifications().then(function(list){
1849                     $scope.classification_list = list;
1850                 });
1851
1852                 createSimpleUpdateWatcher('working.callnumber.classification');
1853                 createSimpleUpdateWatcher('working.callnumber.prefix');
1854                 createSimpleUpdateWatcher('working.callnumber.suffix');
1855             }
1856         ]
1857     }
1858 })
1859
1860