]> git.evergreen-ils.org Git - working/Evergreen.git/blob - Open-ILS/web/js/ui/default/staff/cat/volcopy/app.js
webstaff: volume/copy editor improvements
[working/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 })
29
30 .factory('itemSvc', 
31        ['egCore','$q',
32 function(egCore , $q) {
33
34     var service = {
35         tree : {}, // holds lib->cn->copy hash stack
36         copies : [] // raw copy list
37     };
38
39     // returns a promise resolved with the list of circ mods
40     service.get_classifications = function() {
41         if (egCore.env.acnc)
42             return $q.when(egCore.env.acnc.list);
43
44         return egCore.pcrud.retrieveAll('acnc', null, {atomic : true})
45         .then(function(list) {
46             egCore.env.absorbList(list, 'acnc');
47             return list;
48         });
49     };
50
51     service.get_prefixes = function(org) {
52         return egCore.pcrud.search('acnp',
53             {owning_lib : egCore.org.fullPath(org, true)},
54             null, {atomic : true}
55         );
56
57     };
58
59     service.get_statcats = function(orgs) {
60         return egCore.pcrud.search('asc',
61             {owner : orgs},
62             { flesh : 1,
63               flesh_fields : {
64                 asc : ['owner','entries']
65               }
66             },
67             { atomic : true }
68         );
69     };
70
71     service.get_locations = function(orgs) {
72         return egCore.pcrud.search('acpl',
73             {owning_lib : orgs},
74             null, {atomic : true}
75         );
76     };
77
78     service.get_suffixes = function(org) {
79         return egCore.pcrud.search('acns',
80             {owning_lib : egCore.org.fullPath(org, true)},
81             null, {atomic : true}
82         );
83
84     };
85
86     service.get_statuses = function() {
87         if (egCore.env.ccs)
88             return $q.when(egCore.env.ccs.list);
89
90         return egCore.pcrud.retrieveAll('ccs', {}, {atomic : true}).then(
91             function(list) {
92                 egCore.env.absorbList(list, 'ccs');
93                 return list;
94             }
95         );
96
97     };
98
99     service.get_circ_mods = function() {
100         if (egCore.env.ccm)
101             return $q.when(egCore.env.ccm.list);
102
103         return egCore.pcrud.retrieveAll('ccm', {}, {atomic : true}).then(
104             function(list) {
105                 egCore.env.absorbList(list, 'ccm');
106                 return list;
107             }
108         );
109
110     };
111
112     service.get_circ_types = function() {
113         if (egCore.env.citm)
114             return $q.when(egCore.env.citm.list);
115
116         return egCore.pcrud.retrieveAll('citm', {}, {atomic : true}).then(
117             function(list) {
118                 egCore.env.absorbList(list, 'citm');
119                 return list;
120             }
121         );
122
123     };
124
125     service.get_age_protects = function() {
126         if (egCore.env.crahp)
127             return $q.when(egCore.env.crahp.list);
128
129         return egCore.pcrud.retrieveAll('crahp', {}, {atomic : true}).then(
130             function(list) {
131                 egCore.env.absorbList(list, 'crahp');
132                 return list;
133             }
134         );
135
136     };
137
138     service.bmp_parts = {};
139     service.get_parts = function(rec) {
140         if (service.bmp_parts[rec])
141             return $q.when(service.bmp_parts[rec]);
142
143         return egCore.pcrud.search('bmp',
144             {record : rec},
145             null, {atomic : true}
146         ).then(function(list) {
147             service.bmp_parts[rec] = list;
148             return list;
149         });
150
151     };
152
153     service.flesh = {   
154         flesh : 3, 
155         flesh_fields : {
156             acp : ['call_number','parts','stat_cat_entries'],
157             acn : ['label_class','prefix','suffix']
158         }
159     }
160
161     service.addCopy = function (cp) {
162
163         if (!cp.parts()) cp.parts([]); // just in case...
164
165         var lib = cp.call_number().owning_lib();
166         var cn = cp.call_number().id();
167
168         if (!service.tree[lib]) service.tree[lib] = {};
169         if (!service.tree[lib][cn]) service.tree[lib][cn] = [];
170
171         service.tree[lib][cn].push(cp);
172         service.copies.push(cp);
173     }
174
175     service.fetchIds = function(idList) {
176         service.tree = {}; // clear the tree on fetch
177         service.copies = []; // clear the copy list on fetch
178         return egCore.pcrud.search('acp', { 'id' : idList }, service.flesh).then(null,null,
179             function(copy) {
180                 service.addCopy(copy);
181             }
182         );
183     }
184
185     return service;
186 }])
187
188 .directive("egVolCopyEdit", function () {
189     return {
190         restrict: 'E',
191         replace: true,
192         template:
193             '<div class="row">'+
194                 '<div class="col-xs-5">'+
195                     '<input id="{{callNumber.id()}}.{{copy.id()}}"'+
196                     ' eg-enter="nextBarcode()" class="form-control"'+
197                     ' type="text" ng-model="barcode" ng-change="updateBarcode()"/>'+
198                 '</div>'+
199                 '<div class="col-xs-3"><input class="form-control" type="number" ng-model="copy_number" ng-change="updateCopyNo()"/></div>'+
200                 '<div class="col-xs-4"><eg-basic-combo-box list="parts" selected="part"></eg-basic-combo-box></div>'+
201             '</div>',
202
203         scope: { copy: "=", callNumber: "=", index: "@" },
204         controller : ['$scope','itemSvc',
205             function ( $scope , itemSvc ) {
206                 $scope.new_part_id = 0;
207
208                 $scope.nextBarcode = function (i) {
209                     $scope.$parent.focusNextBarcode($scope.copy.id());
210                 }
211
212                 $scope.updateBarcode = function () { $scope.copy.barcode($scope.barcode); $scope.copy.ischanged(1); };
213                 $scope.updateCopyNo = function () { $scope.copy.copy_number($scope.copy_number); $scope.copy.ischanged(1); };
214                 $scope.updatePart = function () {
215                     var p = $scope.part_list.filter(function (x) {
216                         return x.label() == $scope.part
217                     });
218                     if (p.length > 0) { // preexisting part
219                         $scope.copy.parts(p)
220                     } else { // create one...
221                         var part = new egCore.idl.bmp();
222                         part.id( --$scope.new_part_id );
223                         part.isnew( true );
224                         part.label( $scope.part );
225                         part.record( $scope.callNumber.owning_lib() );
226                         $scope.copy.parts([part]);
227                         $scope.copy.ischanged(1);
228                     }
229                 }
230
231                 $scope.barcode = $scope.copy.barcode();
232                 $scope.copy_number = $scope.copy.copy_number();
233
234                 if ($scope.copy.parts()) {
235                     $scope.part = $scope.copy.parts()[0];
236                     if ($scope.part) $scope.part = $scope.part.label();
237                 };
238
239                 $scope.parts = [];
240                 $scope.part_list = [];
241
242                 itemSvc.get_parts($scope.callNumber.record()).then(function(list){
243                     $scope.part_list = list;
244                     angular.forEach(list, function(p){ $scope.parts.push(p.label()) });
245                 });
246
247             }
248         ]
249
250     }
251 })
252
253 .directive("egVolRow", function () {
254     return {
255         restrict: 'E',
256         replace: true,
257         transclude: true,
258         template:
259             '<div class="row">'+
260                 '<div class="col-xs-2">'+
261                     '<select class="form-control" ng-model="classification" ng-options="cl.name() for cl in classification_list track by idTracker(cl)"/>'+
262                 '</div>'+
263                 '<div class="col-xs-1">'+
264                     '<select class="form-control" ng-model="prefix" ng-change="updatePrefix()" ng-options="p.label() for p in prefix_list track by idTracker(p)"/>'+
265                 '</div>'+
266                 '<div class="col-xs-2"><input class="form-control" type="text" ng-change="updateLabel()" ng-model="label"/></div>'+
267                 '<div class="col-xs-1">'+
268                     '<select class="form-control" ng-model="suffix" ng-change="updateSuffix()" ng-options="s.label() for s in suffix_list track by idTracker(s)"/>'+
269                 '</div>'+
270                 '<div class="col-xs-1"><input class="form-control" type="number" ng-model="copy_count" min="{{orig_copy_count}}" ng-change="changeCPCount()"></div>'+
271                 '<div class="col-xs-5">'+
272                     '<eg-vol-copy-edit ng-repeat="cp in copies track by idTracker(cp)" copy="cp" call-number="callNumber"></eg-vol-copy-edit>'+
273                 '</div>'+
274             '</div>',
275
276         scope: {allcopies: "=", copies: "=" },
277         controller : ['$scope','itemSvc','egCore',
278             function ( $scope , itemSvc , egCore ) {
279                 $scope.new_cp_id = 0;
280                 $scope.callNumber =  $scope.copies[0].call_number();
281
282                 $scope.idTracker = function (x) { if (x) return x.id() };
283
284                 // XXX $() is not working! arg
285                 $scope.focusNextBarcode = function (i) {
286                     var n;
287                     var yep = false;
288                     angular.forEach($scope.copies, function (cp) {
289                         if (n) return;
290
291                         if (cp.id() == i) {
292                             yep = true;
293                             return;
294                         }
295
296                         if (yep) n = cp.id();
297                     });
298
299                     if (n) {
300                         var next = '#' + $scope.callNumber.id() + '.' + n;
301                         var el = $(next).get(0);
302                         if (el) el.focus()
303                     }
304                 }
305
306                 $scope.suffix_list = [];
307                 itemSvc.get_suffixes($scope.callNumber.owning_lib()).then(function(list){
308                     $scope.suffix_list = list;
309                 });
310                 $scope.updateSuffix = function () {
311                     angular.forEach($scope.copies, function(cp) {
312                         cp.call_number().suffix($scope.suffix);
313                         cp.call_number().ischanged(1);
314                     });
315                 }
316
317                 $scope.prefix_list = [];
318                 itemSvc.get_prefixes($scope.callNumber.owning_lib()).then(function(list){
319                     $scope.prefix_list = list;
320                 });
321                 $scope.updatePrefix = function () {
322                     angular.forEach($scope.copies, function(cp) {
323                         cp.call_number().prefix($scope.prefix);
324                         cp.call_number().ischanged(1);
325                     });
326                 }
327
328                 $scope.classification_list = [];
329                 itemSvc.get_classifications().then(function(list){
330                     $scope.classification_list = list;
331                 });
332                 $scope.updateClassification = function () {
333                     angular.forEach($scope.copies, function(cp) {
334                         cp.call_number().label_class($scope.classification);
335                         cp.call_number().ischanged(1);
336                     });
337                 }
338
339                 $scope.updateLabel = function () {
340                     angular.forEach($scope.copies, function(cp) {
341                         cp.call_number().label($scope.label);
342                         cp.call_number().ischanged(1);
343                     });
344                 }
345
346                 $scope.$watch('callNumber.prefix()', function (v) {
347                     if (typeof v != 'object') {
348                         $scope.prefix = $scope.prefix_list.filter(function (p) {
349                             return p.id() == v;
350                         })[0];
351                         $scope.callNumber.prefix($scope.prefix);
352                     }
353                 });
354
355                 $scope.$watch('callNumber.suffix()', function (v) {
356                     if (typeof v != 'object') {
357                         $scope.suffix = $scope.suffix_list.filter( function (s) {
358                             return s.id() == v;
359                         })[0];
360                         $scope.callNumber.suffix($scope.suffix);
361                     }
362                 });
363
364                 $scope.$watch('callNumber.label_class()', function (v) {
365                     if (typeof v != 'object') {
366                         $scope.classification = $scope.classification_list.filter(function (c) {
367                             return c.id() == v;
368                         })[0];
369                         $scope.callNumber.label_class($scope.classification);
370                     }
371                 });
372
373                 $scope.$watch('callNumber.label()', function (v) {
374                     $scope.label = v;
375                 });
376
377                 $scope.prefix = $scope.callNumber.prefix();
378                 $scope.suffix = $scope.callNumber.suffix();
379                 $scope.classification = $scope.callNumber.label_class();
380                 $scope.label = $scope.callNumber.label();
381
382                 $scope.copy_count = $scope.copies.length;
383                 $scope.orig_copy_count = $scope.copy_count;
384
385                 $scope.changeCPCount = function () {
386                     while ($scope.copy_count > $scope.copies.length) {
387                         var cp = new egCore.idl.acp();
388                         cp.id( --$scope.new_cp_id );
389                         cp.isnew( true );
390                         cp.circ_lib( $scope.lib );
391                         cp.call_number( $scope.callNumber );
392                         $scope.copies.push( cp );
393                         $scope.allcopies.push( cp );
394                     }
395
396                     if ($scope.copy_count >= $scope.orig_copy_count) {
397                         var how_many = $scope.copies.length - $scope.copy_count;
398                         if (how_many > 0) {
399                             var dead = $scope.copies.splice($scope.copy_count,how_many);
400                             $scope.callNumber.copies($scope.copies);
401
402                             // Trimming the global list is a bit more tricky
403                             angular.forEach( dead, function (d) {
404                                 angular.forEach( $scope.allcopies, function (l, i) { 
405                                     if (l === d) $scope.allcopies.splice(i,1);
406                                 });
407                             });
408                         }
409                     }
410                 }
411
412             }
413         ]
414
415     }
416 })
417
418 .directive("egVolEdit", function () {
419     return {
420         restrict: 'E',
421         replace: true,
422         template:
423             '<div class="row">'+
424                 '<div class="col-xs-1"><eg-org-selector selected="owning_lib" disableTest="cant_have_vols"></eg-org-selector></div>'+
425                 '<div class="col-xs-1"><input class="form-control" type="number" min="{{orig_cn_count}}" ng-model="cn_count" ng-change="changeCNCount()"/></div>'+
426                 '<div class="col-xs-10">'+
427                     '<eg-vol-row ng-repeat="(cn,copies) in struct track by cn" copies="copies" allcopies="allcopies"></eg-vol-row>'+
428                 '</div>'+
429             '</div>',
430
431         scope: { allcopies: "=", struct: "=", lib: "@", record: "@" },
432         controller : ['$scope','itemSvc','egCore',
433             function ( $scope , itemSvc , egCore ) {
434                 $scope.new_cn_id = 0;
435                 $scope.first_cn = Object.keys($scope.struct)[0];
436                 $scope.full_cn = $scope.struct[$scope.first_cn][0].call_number();
437
438                 $scope.cn_count = Object.keys($scope.struct).length;
439                 $scope.orig_cn_count = $scope.cn_count;
440
441                 $scope.owning_lib = egCore.org.get($scope.lib);
442                 $scope.$watch('owning_lib', function (l) {
443                     angular.forEach( $scope.struct[$scope.first_cn], function (cp) {
444                         cp.call_number().owning_lib( $scope.owning_lib.id() );
445                     });
446                 });
447
448                 $scope.cant_have_vols = function (id) { return !egCore.org.CanHaveVolumes(id); };
449
450                 $scope.$watch('cn_count', function (n) {
451                     var o = Object.keys($scope.struct).length;
452                     if (n > o) { // adding
453                         for (var i = o; o < n; o++) {
454                             var cn = new egCore.idl.acn();
455                             cn.id( --$scope.new_cn_id );
456                             cn.isnew( true );
457                             cn.owning_lib( $scope.owning_lib.id() );
458                             cn.record( $scope.full_cn.record() );
459
460                             var cp = new egCore.idl.acp();
461                             cp.id( --$scope.new_cp_id );
462                             cp.isnew( true );
463                             cp.circ_lib( $scope.owning_lib.id() );
464                             cp.call_number( cn );
465
466                             $scope.struct[cn.id()] = [cp];
467                             $scope.allcopies.push(cp);
468                         }
469                     } else if (n < o && n >= $scope.orig_cn_count) { // removing
470                         var how_many = o - n;
471                         var list = Object
472                                 .keys($scope.struct)
473                                 .sort(function(a, b){return a-b})
474                                 .reverse();
475                         for (var i = how_many; i > 0; i--) {
476                             // Trimming the global list is a bit more tricky
477                             angular.forEach($scope.struct[list[i]], function (d) {
478                                 angular.forEach( $scope.allcopies, function (l, j) { 
479                                     if (l === d) $scope.allcopies.splice(j,1);
480                                 });
481                             });
482                             delete $scope.struct[list[i]];
483                         }
484                     }
485                 });
486             }
487         ]
488
489     }
490 })
491
492 /**
493  * Edit controller!
494  */
495 .controller('EditCtrl', 
496        ['$scope','$q','$routeParams','$location','$timeout','egCore','egNet','egGridDataProvider','itemSvc',
497 function($scope , $q , $routeParams , $location , $timeout , egCore , egNet , egGridDataProvider , itemSvc) {
498
499     $scope.defaults = { // If defaults are not set at all, allow everything
500         statcats : true,
501         attributes : {
502             status : true,
503             loan_duration : true,
504             fine_level : true,
505             cost : true,
506             alerts : true,
507             deposit : true,
508             deposit_amount : true,
509             opac_visible : true,
510             price : true,
511             circulate : true,
512             mint_condition : true,
513             circ_lib : true,
514             ref : true,
515             circ_modifier : true,
516             circ_as_type : true,
517             location : true,
518             holdable : true,
519             age_protect : true
520         }
521     };
522
523     $scope.saveDefaults = function () {
524         egCore.hatch.setItem('cat.copy.defaults', $scope.defaults);
525     }
526
527     $scope.fetchDefaults = function () {
528         egCore.hatch.getItem('cat.copy.defaults').then(function(t) {
529             if (t) {
530                 $scope.defaults = t;
531                 if (!$scope.batch) $scope.batch = {};
532                 $scope.batch.classification = $scope.defaults.classification;
533                 $scope.batch.prefix = $scope.defaults.prefix;
534                 $scope.batch.suffix = $scope.defaults.suffix;
535                 $scope.working.statcat_filter = $scope.defaults.statcat_filter;
536                 if ($scope.defaults.always_vols) $scope.show_vols = true;
537             }
538         });
539     }
540     $scope.fetchDefaults();
541
542     $scope.dirty = false;
543
544     $scope.show_vols = true;
545     $scope.show_copies = true;
546
547     $scope.tracker = function (x,f) { if (x) return x[f]() };
548     $scope.idTracker = function (x) { if (x) return $scope.tracker(x,'id') };
549     $scope.cant_have_vols = function (id) { return !egCore.org.CanHaveVolumes(id); };
550
551     $scope.orgById = function (id) { return egCore.org.get(id) }
552     $scope.statusById = function (id) {
553         return $scope.status_list.filter( function (s) { return s.id() == id } )[0];
554     }
555     $scope.locationById = function (id) {
556         return $scope.location_cache[''+id];
557     }
558
559     $scope.workingToComplete = function () {
560         angular.forEach( $scope.workingGridControls.selectedItems(), function (c) {
561             angular.forEach( itemSvc.copies, function (w, i) {
562                 if (c === w)
563                     $scope.completed_copies = $scope.completed_copies.concat(itemSvc.copies.splice(i,1));
564             });
565         });
566     }
567
568     $scope.completeToWorking = function () {
569         angular.forEach( $scope.completedGridControls.selectedItems(), function (c) {
570             angular.forEach( $scope.completed_copies, function (w, i) {
571                 if (c === w)
572                     itemSvc.copies = itemSvc.copies.concat($scope.completed_copies.splice(i,1));
573             });
574         });
575     }
576
577     createSimpleUpdateWatcher = function (field) {
578         return $scope.$watch('working.' + field, function () {
579             var newval = $scope.working[field];
580
581             if (typeof newval != 'undefined') {
582                 if (angular.isObject(newval)) { // we'll use the pkey
583                     if (newval.id) newval = newval.id();
584                     else if (newval.code) newval = newval.code();
585                 }
586
587                 if (""+newval == "" || newval == null) {
588                     $scope.working[field] = undefined;
589                     newval = null;
590                 }
591
592                 if ($scope.workingGridControls && $scope.workingGridControls.selectedItems) {
593                     angular.forEach(
594                         $scope.workingGridControls.selectedItems(),
595                         function (cp) { cp[field](newval); cp.ischanged(1); }
596                     );
597                 }
598             }
599         });
600     }
601
602     $scope.working = {
603         statcats: {},
604         statcat_filter: undefined
605     };
606
607     $scope.statcatUpdate = function (id) {
608         var newval = $scope.working.statcats[id];
609
610         if (typeof newval != 'undefined') {
611             if (angular.isObject(newval)) { // we'll use the pkey
612                 newval = newval.id();
613             }
614     
615             if (""+newval == "" || newval == null) {
616                 $scope.working.statcats[id] = undefined;
617                 newval = null;
618             }
619     
620             if (!$scope.in_item_select && $scope.workingGridControls && $scope.workingGridControls.selectedItems) {
621                 angular.forEach(
622                     $scope.workingGridControls.selectedItems(),
623                     function (cp) {
624                         cp.stat_cat_entries(
625                             angular.forEach( cp.stat_cat_entries(), function (e) {
626                                 if (e.stat_cat() == id) { // mark deleted
627                                     e.isdeleted(1);
628                                 }
629                             })
630                         );
631     
632                         if (newval) {
633                             var e = new egCore.idl.ascecm();
634                             e.isnew( 1 );
635                             e.owning_copy( cp.id() );
636                             e.stat_cat( id );
637                             e.stat_cat_entry( newval );
638
639                             cp.stat_cat_entries(
640                                 cp.stat_cat_entries().concat([ e ])
641                             );
642
643                         }
644
645                         cp.stat_cat_entries( // trim out ephemeral deleted ones
646                             cp.stat_cat_entries().filter(function (e) {
647                                 if (Boolean(e.isnew())) {
648                                     if (Boolean(e.isdeleted())) {
649                                         return false;
650                                     }
651                                 }
652                                 return true;
653                             })
654                         );
655    
656                         cp.ischanged(1);
657                     }
658                 );
659             }
660         }
661     }
662
663     var dataKey = $routeParams.dataKey;
664     console.debug('dataKey: ' + dataKey);
665
666     if (dataKey && dataKey.length > 0) {
667
668         $scope.templates = {};
669         $scope.template_name = '';
670         $scope.template_name_list = [];
671
672         $scope.fetchTemplates = function () {
673             egCore.hatch.getItem('cat.copy.templates').then(function(t) {
674                 if (t) {
675                     $scope.templates = t;
676                     $scope.template_name_list = Object.keys(t);
677                 }
678             });
679         }
680         $scope.fetchTemplates();
681
682          $scope.applyTemplate = function (n) {
683             angular.forEach($scope.templates[n], function (v,k) {
684                 if (!angular.isObject(v)) {
685                     $scope.working[k] = angular.copy(v);
686                 } else {
687                     angular.forEach(v, function (sv,sk) {
688                         $scope.working[k][sk] = angular.copy(sv);
689                         if (k == 'statcats') $scope.statcatUpdate(sk);
690                     });
691                 }
692             });
693             $scope.template_name = '';
694         }
695
696         $scope.copytab = 'working';
697         $scope.tab = 'edit';
698         $scope.summaryRecord = null;
699         $scope.record_id = null;
700         $scope.data = {};
701         $scope.completed_copies = [];
702         $scope.location_orgs = [];
703         $scope.location_cache = {};
704         $scope.statcats = [];
705         if (!$scope.batch) $scope.batch = {};
706
707         $scope.applyBatchCNValues = function () {
708             if ($scope.data.tree) {
709                 angular.forEach($scope.data.tree, function(cn_hash) {
710                     angular.forEach(cn_hash, function(copies) {
711                         angular.forEach(copies, function(cp) {
712                             if (typeof $scope.batch.classification != 'undefined' && $scope.batch.classification != '')
713                                 cp.call_number().label_class($scope.batch.classification);
714                             if (typeof $scope.batch.prefix != 'undefined' && $scope.batch.prefix != '')
715                                 cp.call_number().prefix($scope.batch.prefix);
716                             if (typeof $scope.batch.label != 'undefined' && $scope.batch.label != '')
717                                 cp.call_number().label($scope.batch.label);
718                             if (typeof $scope.batch.suffix != 'undefined' && $scope.batch.suffix != '')
719                                 cp.call_number().suffix($scope.batch.suffix);
720                         });
721                     });
722                 });
723             }
724         }
725
726         $scope.clearWorking = function () {
727             angular.forEach($scope.working, function (v,k,o) {
728                 if (!angular.isObject(v)) {
729                     if (typeof v != 'undefined')
730                         $scope.working[k] = undefined;
731                 } else if (k != 'circ_lib') {
732                     angular.forEach(v, function (sv,sk) {
733                         if (typeof v != 'undefined')
734                             $scope.working[k][sk] = undefined;
735                     });
736                 }
737             });
738             $scope.working.circ_lib = undefined; // special
739         }
740
741         $scope.completedGridDataProvider = egGridDataProvider.instance({
742             get : function(offset, count) {
743                 //return provider.arrayNotifier(itemSvc.copies, offset, count);
744                 return this.arrayNotifier($scope.completed_copies, offset, count);
745             }
746         });
747
748         $scope.completedGridControls = {};
749
750         $scope.workingGridDataProvider = egGridDataProvider.instance({
751             get : function(offset, count) {
752                 //return provider.arrayNotifier(itemSvc.copies, offset, count);
753                 return this.arrayNotifier(itemSvc.copies, offset, count);
754             }
755         });
756
757         $scope.workingGridControls = {};
758
759         egNet.request(
760             'open-ils.actor',
761             'open-ils.actor.anon_cache.get_value',
762             dataKey, 'edit-these-copies'
763         ).then(function (data) {
764
765             if (data) {
766                 if (data.hide_vols && !$scope.defaults.always_vols) $scope.show_vols = false;
767                 if (data.hide_copies) $scope.show_copies = false;
768
769                 $scope.record_id = data.record_id;
770
771                 if (data.copies && data.copies.length)
772                     return itemSvc.fetchIds(data.copies);
773
774                 if (data.raw && data.raw.length) {
775
776                     /* data.raw must be an array of copies with (at least)
777                      * the call number fleshed on each.  For new copies
778                      * create from whole cloth, the id for each should
779                      * probably be negative and isnew() should return true.
780                      * Each /distinct/ call number must have a distinct id
781                      * as well, probably negative also if they're new. Clear?
782                      */
783
784                     angular.forEach(
785                         data.raw,
786                         function (cp) { itemSvc.addCopy(cp) }
787                     );
788
789                     return itemSvc.copies;
790                 }
791             }
792
793         }).then( function() {
794             $scope.data = itemSvc;
795             $scope.workingGridDataProvider.refresh();
796         });
797
798         $scope.in_item_select = false;
799         $scope.afterItemSelect = function() { $scope.in_item_select = false };
800         $scope.handleItemSelect = function (item_list) {
801             if (item_list && item_list.length > 0) {
802                 $scope.in_item_select = true;
803
804                 angular.forEach(Object.keys($scope.defaults.attributes), function (attr) {
805
806                     var value_hash = {};
807                     angular.forEach(item_list, function (item) {
808                         if (item[attr]) {
809                             var v = item[attr]()
810                             if (angular.isObject(v)) {
811                                 if (v.id) v = v.id();
812                                 else if (v.code) v = v.code();
813                             }
814                             value_hash[v] = 1;
815                         }
816                     });
817
818                     if (Object.keys(value_hash).length == 1) {
819                         if (attr == 'circ_lib') {
820                             $scope.working[attr] = egCore.org.get(item_list[0][attr]());
821                         } else {
822                             $scope.working[attr] = item_list[0][attr]();
823                         }
824                     } else {
825                         $scope.working[attr] = undefined;
826                     }
827                 });
828
829                 angular.forEach($scope.statcats, function (sc) {
830
831                     var counter = -1;
832                     var value_hash = {};
833                     var none = false;
834                     angular.forEach(item_list, function (item) {
835                         if (item.stat_cat_entries()) {
836                             if (item.stat_cat_entries().length > 0) {
837                                 var right_sc = item.stat_cat_entries().filter(function (e) {
838                                     return e.stat_cat() == sc.id() && !Boolean(e.isdeleted());
839                                 });
840
841                                 if (right_sc.length > 0) {
842                                     value_hash[right_sc[0].stat_cat_entry()] = right_sc[0].stat_cat_entry();
843                                 } else {
844                                     none = true;
845                                 }
846                             }
847                         } else {
848                             none = true;
849                         }
850                     });
851
852                     if (!none && Object.keys(value_hash).length == 1) {
853                         $scope.working.statcats[sc.id()] = value_hash[Object.keys(value_hash)[0]];
854                     } else {
855                         $scope.working.statcats[sc.id()] = undefined;
856                     }
857                 });
858
859             } else {
860                 $scope.clearWorking();
861             }
862
863         }
864
865         $scope.$watch('data.copies.length', function () {
866             if ($scope.data.copies) {
867                 var base_orgs = $scope.data.copies.map(function(cp){
868                     return cp.circ_lib()
869                 }).concat(
870                     $scope.data.copies.map(function(cp){
871                         return cp.call_number().owning_lib()
872                     })
873                 ).concat(
874                     [egCore.auth.user().ws_ou()]
875                 ).filter(function(e,i,a){
876                     return a.lastIndexOf(e) === i;
877                 });
878
879                 var all_orgs = [];
880                 angular.forEach(base_orgs, function(o) {
881                     all_orgs = all_orgs.concat( egCore.org.fullPath(o, true) );
882                 });
883
884                 var final_orgs = all_orgs.filter(function(e,i,a){
885                     return a.lastIndexOf(e) === i;
886                 }).sort(function(a,b){return b-a});
887
888                 if ($scope.location_orgs.toString() != final_orgs.toString()) {
889                     $scope.location_orgs = final_orgs;
890                     if ($scope.location_orgs.length) {
891                         itemSvc.get_locations($scope.location_orgs).then(function(list){
892                             angular.forEach(list, function(l) {
893                                 $scope.location_cache[ ''+l.id() ] = l;
894                             });
895                             $scope.location_list = list;
896                         });
897
898                         $scope.statcat_filter_list = [];
899                         angular.forEach($scope.location_orgs, function (o) {
900                             $scope.statcat_filter_list.push(egCore.org.get(o));
901                         });
902
903                         itemSvc.get_statcats($scope.location_orgs).then(function(list){
904                             $scope.statcats = list;
905                             angular.forEach($scope.statcats, function (s) {
906
907                                 if (!$scope.working)
908                                     $scope.working = { statcats: {}, statcat_filter: undefined};
909                                 if (!$scope.working.statcats)
910                                     $scope.working.statcats = {};
911
912                                 if (!$scope.in_item_select) {
913                                     $scope.working.statcats[s.id()] = undefined;
914                                 }
915                                 createStatcatUpdateWatcher(s.id());
916                             });
917                             $scope.in_item_select = false;
918                         });
919                     }
920                 }
921             }
922
923             $scope.workingGridDataProvider.refresh();
924         });
925
926         $scope.suffix_list = [];
927         itemSvc.get_suffixes(egCore.auth.user().ws_ou()).then(function(list){
928             $scope.suffix_list = list;
929         });
930
931         $scope.prefix_list = [];
932         itemSvc.get_prefixes(egCore.auth.user().ws_ou()).then(function(list){
933             $scope.prefix_list = list;
934         });
935
936         $scope.classification_list = [];
937         itemSvc.get_classifications().then(function(list){
938             $scope.classification_list = list;
939         });
940
941         $scope.$watch('completed_copies.length', function () {
942             $scope.completedGridDataProvider.refresh();
943         });
944
945         $scope.location_list = [];
946         itemSvc.get_locations().then(function(list){
947             $scope.location_list = list;
948         });
949         createSimpleUpdateWatcher('location');
950
951         $scope.status_list = [];
952         itemSvc.get_statuses().then(function(list){
953             $scope.status_list = list;
954         });
955         createSimpleUpdateWatcher('status');
956
957         $scope.circ_modifier_list = [];
958         itemSvc.get_circ_mods().then(function(list){
959             $scope.circ_modifier_list = list;
960         });
961         createSimpleUpdateWatcher('circ_modifier');
962
963         $scope.circ_type_list = [];
964         itemSvc.get_circ_types().then(function(list){
965             $scope.circ_type_list = list;
966         });
967         createSimpleUpdateWatcher('circ_as_type');
968
969         $scope.age_protect_list = [];
970         itemSvc.get_age_protects().then(function(list){
971             $scope.age_protect_list = list;
972         });
973         createSimpleUpdateWatcher('age_protect');
974
975         createSimpleUpdateWatcher('circ_lib');
976         createSimpleUpdateWatcher('circulate');
977         createSimpleUpdateWatcher('holdable');
978         createSimpleUpdateWatcher('fine_level');
979         createSimpleUpdateWatcher('loan_duration');
980         createSimpleUpdateWatcher('cost');
981         createSimpleUpdateWatcher('deposit');
982         createSimpleUpdateWatcher('deposit_amount');
983         createSimpleUpdateWatcher('mint_condition');
984         createSimpleUpdateWatcher('opac_visible');
985         createSimpleUpdateWatcher('ref');
986
987     }
988
989 }])
990
991 .directive("egVolTemplate", function () {
992     return {
993         restrict: 'E',
994         replace: true,
995         template: '<div ng-include="'+"'/eg/staff/cat/volcopy/t_attr_edit'"+'"></div>',
996         scope: { },
997         controller : ['$scope','itemSvc','egCore',
998             function ( $scope , itemSvc , egCore ) {
999
1000                 $scope.defaults = { // If defaults are not set at all, allow everything
1001                     statcats : true,
1002                     attributes : {
1003                         status : true,
1004                         loan_duration : true,
1005                         fine_level : true,
1006                         cost : true,
1007                         alerts : true,
1008                         deposit : true,
1009                         deposit_amount : true,
1010                         opac_visible : true,
1011                         price : true,
1012                         circulate : true,
1013                         mint_condition : true,
1014                         circ_lib : true,
1015                         ref : true,
1016                         circ_modifier : true,
1017                         circ_as_type : true,
1018                         location : true,
1019                         holdable : true,
1020                         age_protect : true
1021                     }
1022                 };
1023
1024                 $scope.fetchDefaults = function () {
1025                     egCore.hatch.getItem('cat.copy.defaults').then(function(t) {
1026                         if (t) {
1027                             $scope.defaults = t;
1028                             $scope.working.statcat_filter = $scope.defaults.statcat_filter;
1029                         }
1030                     });
1031                 }
1032                 $scope.fetchDefaults();
1033
1034                 $scope.dirty = false;
1035                 $scope.template_controls = true;
1036
1037                 $scope.fetchTemplates = function () {
1038                     egCore.hatch.getItem('cat.copy.templates').then(function(t) {
1039                         if (t) {
1040                             $scope.templates = t;
1041                             $scope.template_name_list = Object.keys(t);
1042                         }
1043                     });
1044                 }
1045                 $scope.fetchTemplates();
1046             
1047                 $scope.applyTemplate = function (n) {
1048                     angular.forEach($scope.templates[n], function (v,k) {
1049                         if (!angular.isObject(v)) {
1050                             $scope.working[k] = angular.copy(v);
1051                         } else {
1052                             angular.forEach(v, function (sv,sk) {
1053                                 $scope.working[k][sk] = angular.copy(sv);
1054                             });
1055                         }
1056                     });
1057                     $scope.template_name = '';
1058                 }
1059
1060                 $scope.deleteTemplate = function (n) {
1061                     if (n) {
1062                         delete $scope.templates[n]
1063                         $scope.template_name_list = Object.keys($scope.templates);
1064                         $scope.template_name = '';
1065                         egCore.hatch.setItem('cat.copy.templates', $scope.templates);
1066                         $scope.$parent.fetchTemplates();
1067                     }
1068                 }
1069
1070                 $scope.saveTemplate = function (n) {
1071                     if (n) {
1072                         var tmpl = {};
1073             
1074                         angular.forEach($scope.working, function (v,k) {
1075                             if (angular.isObject(v)) { // we'll use the pkey
1076                                 if (v.id) v = v.id();
1077                                 else if (v.code) v = v.code();
1078                             }
1079             
1080                             tmpl[k] = v;
1081                         });
1082             
1083                         $scope.templates[n] = tmpl;
1084                         $scope.template_name_list = Object.keys($scope.templates);
1085             
1086                         egCore.hatch.setItem('cat.copy.templates', $scope.templates);
1087                         $scope.$parent.fetchTemplates();
1088                     }
1089                 }
1090             
1091                 $scope.templates = {};
1092                 $scope.template_name = '';
1093                 $scope.template_name_list = [];
1094             
1095                 $scope.tracker = function (x,f) { if (x) return x[f]() };
1096                 $scope.idTracker = function (x) { if (x) return $scope.tracker(x,'id') };
1097                 $scope.cant_have_vols = function (id) { return !egCore.org.CanHaveVolumes(id); };
1098             
1099                 $scope.orgById = function (id) { return egCore.org.get(id) }
1100                 $scope.statusById = function (id) {
1101                     return $scope.status_list.filter( function (s) { return s.id() == id } )[0];
1102                 }
1103                 $scope.locationById = function (id) {
1104                     return $scope.location_cache[''+id];
1105                 }
1106             
1107                 createSimpleUpdateWatcher = function (field) {
1108                     $scope.$watch('working.' + field, function () {
1109                         var newval = $scope.working[field];
1110             
1111                         if (typeof newval != 'undefined') {
1112                             $scope.dirty = true;
1113                             if (angular.isObject(newval)) { // we'll use the pkey
1114                                 if (newval.id) $scope.working[field] = newval.id();
1115                                 else if (newval.code) $scope.working[field] = newval.code();
1116                             }
1117             
1118                             if (""+newval == "" || newval == null) {
1119                                 $scope.working[field] = undefined;
1120                             }
1121             
1122                         }
1123                     });
1124                 }
1125             
1126                 $scope.working = {
1127                     statcats: {},
1128                     statcat_filter: undefined
1129                 };
1130             
1131                 createStatcatUpdateWatcher = function (id) {
1132                     return $scope.$watch('working.statcats[' + id + ']', function () {
1133                         if ($scope.working.statcats) {
1134                             var newval = $scope.working.statcats[id];
1135                 
1136                             if (typeof newval != 'undefined') {
1137                                 if (angular.isObject(newval)) { // we'll use the pkey
1138                                     newval = newval.id();
1139                                 }
1140                 
1141                                 if (""+newval == "" || newval == null) {
1142                                     $scope.working.statcats[id] = undefined;
1143                                     newval = null;
1144                                 }
1145                 
1146                             }
1147                         }
1148                     });
1149                 }
1150
1151                 $scope.clearWorking = function () {
1152                     angular.forEach($scope.working, function (v,k,o) {
1153                         if (!angular.isObject(v)) {
1154                             if (typeof v != 'undefined')
1155                                 $scope.working[k] = undefined;
1156                         } else if (k != 'circ_lib') {
1157                             angular.forEach(v, function (sv,sk) {
1158                                 $scope.working[k][sk] = undefined;
1159                             });
1160                         }
1161                     });
1162                     $scope.working.circ_lib = undefined; // special
1163                 }
1164
1165                 $scope.working = {};
1166                 $scope.location_orgs = [];
1167                 $scope.location_cache = {};
1168             
1169                 $scope.location_list = [];
1170                 itemSvc.get_locations(
1171                     egCore.org.fullPath( egCore.auth.user().ws_ou(), true )
1172                 ).then(function(list){
1173                     $scope.location_list = list;
1174                 });
1175                 createSimpleUpdateWatcher('location');
1176
1177                 $scope.statcat_filter_list = egCore.org.fullPath( egCore.auth.user().ws_ou() );
1178
1179                 $scope.statcats = [];
1180                 itemSvc.get_statcats(
1181                     egCore.org.fullPath( egCore.auth.user().ws_ou(), true )
1182                 ).then(function(list){
1183                     $scope.statcats = list;
1184                     angular.forEach($scope.statcats, function (s) {
1185
1186                         if (!$scope.working)
1187                             $scope.working = { statcats: {}, statcat_filter: undefined};
1188                         if (!$scope.working.statcats)
1189                             $scope.working.statcats = {};
1190
1191                         $scope.working.statcats[s.id()] = undefined;
1192                         createStatcatUpdateWatcher(s.id());
1193                     });
1194                 });
1195             
1196                 $scope.status_list = [];
1197                 itemSvc.get_statuses().then(function(list){
1198                     $scope.status_list = list;
1199                 });
1200                 createSimpleUpdateWatcher('status');
1201             
1202                 $scope.circ_modifier_list = [];
1203                 itemSvc.get_circ_mods().then(function(list){
1204                     $scope.circ_modifier_list = list;
1205                 });
1206                 createSimpleUpdateWatcher('circ_modifier');
1207             
1208                 $scope.circ_type_list = [];
1209                 itemSvc.get_circ_types().then(function(list){
1210                     $scope.circ_type_list = list;
1211                 });
1212                 createSimpleUpdateWatcher('circ_as_type');
1213             
1214                 $scope.age_protect_list = [];
1215                 itemSvc.get_age_protects().then(function(list){
1216                     $scope.age_protect_list = list;
1217                 });
1218                 createSimpleUpdateWatcher('age_protect');
1219             
1220                 createSimpleUpdateWatcher('circ_lib');
1221                 createSimpleUpdateWatcher('circulate');
1222                 createSimpleUpdateWatcher('holdable');
1223                 createSimpleUpdateWatcher('fine_level');
1224                 createSimpleUpdateWatcher('loan_duration');
1225                 createSimpleUpdateWatcher('cost');
1226                 createSimpleUpdateWatcher('deposit');
1227                 createSimpleUpdateWatcher('deposit_amount');
1228                 createSimpleUpdateWatcher('mint_condition');
1229                 createSimpleUpdateWatcher('opac_visible');
1230                 createSimpleUpdateWatcher('ref');
1231             }
1232         ]
1233     }
1234 })
1235
1236