[open-ils-commits] [GIT] Evergreen ILS branch master updated. 7b41b6654f124da9c6d3f3ce39c0f351c714c35d

Evergreen Git git at git.evergreen-ils.org
Fri Aug 25 15:38:33 EDT 2017


This is an automated email from the git hooks/post-receive script. It was
generated because a ref change was pushed to the repository containing
the project "Evergreen ILS".

The branch, master has been updated
       via  7b41b6654f124da9c6d3f3ce39c0f351c714c35d (commit)
       via  5ec2a27f0aa2645f3e63ac11c5f9a8ea08154fca (commit)
       via  a45608038328700ea54d52582096abf22704eda0 (commit)
       via  91771c5f535892f909ab0bf30bd4a15fe1338f6f (commit)
       via  85852d50014b7527d7775d874ae257e4fb14eeaf (commit)
       via  85f6c2b8715a29d6c92c29132d2b05469de66e64 (commit)
      from  42528f5dd56f63ab4d143c3f05872f96a1c3132a (commit)

Those revisions listed above that are new to this repository have
not appeared on any other notification email; so we list those
revisions in full, below.

- Log -----------------------------------------------------------------
commit 7b41b6654f124da9c6d3f3ce39c0f351c714c35d
Author: Galen Charlton <gmc at equinoxinitiative.org>
Date:   Fri Aug 25 15:51:44 2017 -0400

    LP#1685929: (follow-up) fix passing copy when opening volume/copy editor
    
    Signed-off-by: Galen Charlton <gmc at equinoxinitiative.org>

diff --git a/Open-ILS/web/js/ui/default/staff/circ/checkin/app.js b/Open-ILS/web/js/ui/default/staff/circ/checkin/app.js
index bbc8bbe..62713e4 100644
--- a/Open-ILS/web/js/ui/default/staff/circ/checkin/app.js
+++ b/Open-ILS/web/js/ui/default/staff/circ/checkin/app.js
@@ -361,8 +361,10 @@ function($scope , $q , $window , $location , $timeout , egCore , checkinSvc , eg
         var itemObjs = [];
         angular.forEach(items, function(i){
             var h = egCore.idl.toHash(i);
-            h['call_number.record.id'] = h.record.doc_id;
-            itemObjs.push(h);
+            itemObjs.push({
+                'call_number.record.id': h.record.doc_id,
+                'id' : h.acp.id
+            });
         });
         itemSvc.spawnHoldingsEdit(itemObjs,false,false);
     }

commit 5ec2a27f0aa2645f3e63ac11c5f9a8ea08154fca
Author: Galen Charlton <gmc at equinoxinitiative.org>
Date:   Fri Aug 25 15:17:25 2017 -0400

    LP#1685929: adjust egItem.print_spine_labels()
    
    Make egItem.print_spine_labels() accept a list of copy
    IDs and make the caller responsible for putting that list together.
    
    Signed-off-by: Galen Charlton <gmc at equinoxinitiative.org>

diff --git a/Open-ILS/web/js/ui/default/staff/circ/checkin/app.js b/Open-ILS/web/js/ui/default/staff/circ/checkin/app.js
index 7f86eb3..bbc8bbe 100644
--- a/Open-ILS/web/js/ui/default/staff/circ/checkin/app.js
+++ b/Open-ILS/web/js/ui/default/staff/circ/checkin/app.js
@@ -376,7 +376,11 @@ function($scope , $q , $window , $location , $timeout , egCore , checkinSvc , eg
     }
 
     $scope.printSpineLabels = function(items){
-        itemSvc.print_spine_labels(items);
+        var copy_ids = [];
+        angular.forEach(items, function(item) {
+            if (item.acp) copy_ids.push(item.acp.id());
+        });
+        itemSvc.print_spine_labels(copy_ids);
     }
 }])
 
diff --git a/Open-ILS/web/js/ui/default/staff/circ/services/item.js b/Open-ILS/web/js/ui/default/staff/circ/services/item.js
index 278e433..28eae2a 100644
--- a/Open-ILS/web/js/ui/default/staff/circ/services/item.js
+++ b/Open-ILS/web/js/ui/default/staff/circ/services/item.js
@@ -887,12 +887,12 @@ function(egCore , egCirc , $uibModal , $q , $timeout , $window , egConfirmDialog
         });
     }
 
-    service.print_spine_labels = function(copies){
+    service.print_spine_labels = function(copy_ids){
         egCore.net.request(
             'open-ils.actor',
             'open-ils.actor.anon_cache.set_value',
             null, 'print-labels-these-copies', {
-                copies : service.gatherSelectedHoldingsIds(copies)
+                copies : copy_ids
             }
         ).then(function(key) {
             if (key) {

commit a45608038328700ea54d52582096abf22704eda0
Author: Galen Charlton <gmc at equinoxinitiative.org>
Date:   Fri Aug 25 11:49:31 2017 -0400

    LP#1685929: (follow-up) fix rebase error
    
    Intentionally not putting two actions in the Show group.
    
    Signed-off-by: Galen Charlton <gmc at equinoxinitiative.org>

diff --git a/Open-ILS/src/templates/staff/circ/checkin/t_checkin_table.tt2 b/Open-ILS/src/templates/staff/circ/checkin/t_checkin_table.tt2
index 5978e7b..4d34a61 100644
--- a/Open-ILS/src/templates/staff/circ/checkin/t_checkin_table.tt2
+++ b/Open-ILS/src/templates/staff/circ/checkin/t_checkin_table.tt2
@@ -20,12 +20,10 @@
     label="[% l('Backdate Post-Checkin') %]">
   </eg-grid-action>
   <eg-grid-action
-    group="[% l('Show')"
     handler="showMarkDamaged"
     label="[% l('Mark Items Damaged') %]">
   </eg-grid-action>
   <eg-grid-action
-    group="[% l('Show')"
     handler="show_mark_missing_pieces"
     label="[% l('Mark Missing Pieces') %]">
   </eg-grid-action>

commit 91771c5f535892f909ab0bf30bd4a15fe1338f6f
Author: Cesar Velez <Cesar.Velez at equinoxinitiative.org>
Date:   Tue Aug 1 22:42:00 2017 +0100

    LP#1685929: add implementation print spine labels to checkin grid
    
    Signed-off by: Cesar Velez <cesar.velez at equinoxinitiative.org>
    Signed-off-by: Galen Charlton <gmc at equinoxinitiative.org>

diff --git a/Open-ILS/web/js/ui/default/staff/circ/checkin/app.js b/Open-ILS/web/js/ui/default/staff/circ/checkin/app.js
index e3bf69a..7f86eb3 100644
--- a/Open-ILS/web/js/ui/default/staff/circ/checkin/app.js
+++ b/Open-ILS/web/js/ui/default/staff/circ/checkin/app.js
@@ -374,5 +374,9 @@ function($scope , $q , $window , $location , $timeout , egCore , checkinSvc , eg
             itemSvc.mark_missing_pieces(i.acp);
         });
     }
+
+    $scope.printSpineLabels = function(items){
+        itemSvc.print_spine_labels(items);
+    }
 }])
 
diff --git a/Open-ILS/web/js/ui/default/staff/circ/services/item.js b/Open-ILS/web/js/ui/default/staff/circ/services/item.js
index 3c0953f..278e433 100644
--- a/Open-ILS/web/js/ui/default/staff/circ/services/item.js
+++ b/Open-ILS/web/js/ui/default/staff/circ/services/item.js
@@ -887,5 +887,22 @@ function(egCore , egCirc , $uibModal , $q , $timeout , $window , egConfirmDialog
         });
     }
 
+    service.print_spine_labels = function(copies){
+        egCore.net.request(
+            'open-ils.actor',
+            'open-ils.actor.anon_cache.set_value',
+            null, 'print-labels-these-copies', {
+                copies : service.gatherSelectedHoldingsIds(copies)
+            }
+        ).then(function(key) {
+            if (key) {
+                var url = egCore.env.basePath + 'cat/printlabels/' + key;
+                $timeout(function() { $window.open(url, '_blank') });
+            } else {
+                alert('Could not create anonymous cache key!');
+            }
+        });
+    }
+
     return service;
 }])

commit 85852d50014b7527d7775d874ae257e4fb14eeaf
Author: Cesar Velez <Cesar.Velez at equinoxinitiative.org>
Date:   Fri Aug 11 16:11:45 2017 +0100

    LP#1685929 - Extract itemSvc from item module to core
    
    Refactored Missing Pieces module and CheckIn to use
    shared functionally of itemSvc, which is now it's own
    separate file.
    
    Signed-off by: Cesar Velez <cesar.velez at equinoxinitiative.org>
    Signed-off-by: Galen Charlton <gmc at equinoxinitiative.org>
    
    Conflicts:
    	Open-ILS/web/js/ui/default/staff/cat/item/app.js

diff --git a/Open-ILS/src/templates/staff/cat/item/index.tt2 b/Open-ILS/src/templates/staff/cat/item/index.tt2
index 7e02382..df5ed04 100644
--- a/Open-ILS/src/templates/staff/cat/item/index.tt2
+++ b/Open-ILS/src/templates/staff/cat/item/index.tt2
@@ -14,6 +14,7 @@
 <script src="[% ctx.media_prefix %]/js/ui/default/staff/cat/services/record.js"></script>
 <script src="[% ctx.media_prefix %]/js/ui/default/staff/circ/services/billing.js"></script>
 <script src="[% ctx.media_prefix %]/js/ui/default/staff/circ/services/circ.js"></script>
+<script src="[% ctx.media_prefix %]/js/ui/default/staff/circ/services/item.js"></script>
 [% INCLUDE 'staff/circ/share/circ_strings.tt2' %]
 <script>
   angular.module('egCoreMod').run(['egStrings', function(s) {
diff --git a/Open-ILS/src/templates/staff/cat/item/missing_pieces.tt2 b/Open-ILS/src/templates/staff/cat/item/missing_pieces.tt2
index c8751aa..387cc4a 100644
--- a/Open-ILS/src/templates/staff/cat/item/missing_pieces.tt2
+++ b/Open-ILS/src/templates/staff/cat/item/missing_pieces.tt2
@@ -8,6 +8,7 @@
 [% BLOCK APP_JS %]
 <script src="[% ctx.media_prefix %]/js/ui/default/staff/cat/item/missing_pieces.js"></script>
 <script src="[% ctx.media_prefix %]/js/ui/default/staff/circ/services/circ.js"></script>
+<script src="[% ctx.media_prefix %]/js/ui/default/staff/circ/services/item.js"></script>
 <script>
 angular.module('egCoreMod').run(['egStrings', function(s) {
 s.CONFIRM_MARK_MISSING_TITLE = "[% l('Mark item as missing pieces?') %]";
diff --git a/Open-ILS/src/templates/staff/circ/checkin/index.tt2 b/Open-ILS/src/templates/staff/circ/checkin/index.tt2
index 4bc925d6..cfd0a1b 100644
--- a/Open-ILS/src/templates/staff/circ/checkin/index.tt2
+++ b/Open-ILS/src/templates/staff/circ/checkin/index.tt2
@@ -9,6 +9,7 @@
 <script src="[% ctx.media_prefix %]/js/ui/default/staff/services/ui.js"></script>
 <script src="[% ctx.media_prefix %]/js/ui/default/staff/services/user.js"></script>
 <script src="[% ctx.media_prefix %]/js/ui/default/staff/circ/services/circ.js"></script>
+<script src="[% ctx.media_prefix %]/js/ui/default/staff/circ/services/item.js"></script>
 <script>
 angular.module('egCoreMod').run(['egStrings', function(s) {
 s.CONFIRM_MARK_MISSING_TITLE = "[% l('Mark item as missing pieces?') %]";
@@ -20,7 +21,6 @@ s.CIRC_NOT_FOUND =
 </script>
 
 [% INCLUDE 'staff/circ/share/circ_strings.tt2' %]
-<script src="[% ctx.media_prefix %]/js/ui/default/staff/cat/item/app.js"></script>
 <script src="[% ctx.media_prefix %]/js/ui/default/staff/circ/checkin/app.js"></script>
 <link rel="stylesheet" href="[% ctx.base_path %]/staff/css/circ.css" />
 [% END %]
diff --git a/Open-ILS/web/js/ui/default/staff/cat/item/app.js b/Open-ILS/web/js/ui/default/staff/cat/item/app.js
index be24fd8..2eac888 100644
--- a/Open-ILS/web/js/ui/default/staff/cat/item/app.js
+++ b/Open-ILS/web/js/ui/default/staff/cat/item/app.js
@@ -47,899 +47,12 @@ angular.module('egItemStatus',
     $routeProvider.otherwise({redirectTo : '/cat/item/search'});
 })
 
-.factory('itemSvc', 
-       ['egCore','egCirc','$uibModal','$q','$timeout','$window','egConfirmDialog','egAlertDialog',
-function(egCore , egCirc , $uibModal , $q , $timeout , $window , egConfirmDialog, egAlertDialog ) {
-
-    var service = {
-        copies : [], // copy barcode search results
-        index : 0 // search grid index
-    };
-
-    service.flesh = {   
-        flesh : 3, 
-        flesh_fields : {
-            acp : ['call_number','location','status','location','floating','circ_modifier',
-                'age_protect','circ_lib'],
-            acn : ['record','prefix','suffix','label_class'],
-            bre : ['simple_record','creator','editor']
-        },
-        select : { 
-            // avoid fleshing MARC on the bre
-            // note: don't add simple_record.. not sure why
-            bre : ['id','tcn_value','creator','editor'],
-        } 
-    }
-
-    service.circFlesh = {
-        flesh : 2,
-        flesh_fields : {
-            circ : [
-                'usr',
-                'workstation',
-                'checkin_workstation',
-                'checkin_lib',
-                'duration_rule',
-                'max_fine_rule',
-                'recurring_fine_rule'
-            ],
-            au : ['card']
-        },
-        order_by : {circ : 'xact_start desc'},
-        limit :  1
-    }
-
-    //Retrieve separate copy, aacs, and accs information
-    service.getCopy = function(barcode, id) {
-        if (barcode) {
-            // handle barcode completion
-            return egCirc.handle_barcode_completion(barcode)
-            .then(function(actual_barcode) {
-                return egCore.pcrud.search(
-                    'acp', {barcode : actual_barcode, deleted : 'f'},
-                    service.flesh).then(function(copy) {return copy});
-            });
-        }
-
-        return egCore.pcrud.retrieve( 'acp', id, service.flesh)
-            .then(function(copy) {return copy});
-    }
-
-    service.getCirc = function(id) {
-        return egCore.pcrud.search('aacs', { target_copy : id },
-            service.circFlesh).then(function(circ) {return circ});
-    }
-
-    service.getSummary = function(id) {
-        return circ_summary = egCore.net.request(
-            'open-ils.circ',
-            'open-ils.circ.renewal_chain.retrieve_by_circ.summary',
-            egCore.auth.token(), id).then(
-                function(circ_summary) {return circ_summary});
-    }
-
-    //Combine copy, circ, and accs information
-    service.retrieveCopyData = function(barcode, id) {
-        var copyData = {};
-
-        var fetchCopy = function(barcode, id) {
-            return service.getCopy(barcode, id)
-                .then(function(copy) {
-                    copyData.copy = copy;
-                    return copyData;
-                });
-        }
-        var fetchCirc = function(copy) {
-            return service.getCirc(copy.id())
-                .then(function(circ) {
-                    copyData.circ = circ;
-                    return copyData;
-                });
-        }
-        var fetchSummary = function(circ) {
-            return service.getSummary(circ.id())
-                .then(function(summary) {
-                    copyData.circ_summary = summary;
-                    return copyData;
-                });
-        }
-        return fetchCopy(barcode, id).then(function(res) {
-            return fetchCirc(copyData.copy).then(function(res) {
-                if (copyData.circ) {
-                    return fetchSummary(copyData.circ).then(function() {
-                        return copyData;
-                    });
-                } else {
-                    return copyData;
-                }
-            });
-        });
-
-    }
-
-    // resolved with the last received copy
-    service.fetch = function(barcode, id, noListDupes) {
-        var copy;
-        var circ;
-        var circ_summary;
-        var lastRes = {};
-
-        return service.retrieveCopyData(barcode, id)
-        .then(function(copyData) {
-            //Make sure we're getting a completed copyData - no plain acp or circ objects
-            if (copyData.circ) {
-                // flesh circ_lib locally
-                copyData.circ.circ_lib(egCore.org.get(copyData.circ.circ_lib()));
-                copyData.circ.checkin_workstation(
-                    egCore.org.get(copyData.circ.checkin_workstation()));
-            }
-            var flatCopy;
-
-            if (noListDupes) {
-                // use the existing copy if possible
-                flatCopy = service.copies.filter(
-                    function(c) {return c.id == copyData.copy.id()})[0];
-            }
-
-            if (!flatCopy) {
-                flatCopy = egCore.idl.toHash(copyData.copy, true);
-
-                if (copyData.circ) {
-                    flatCopy._circ = egCore.idl.toHash(copyData.circ, true);
-                    flatCopy._circ_summary = egCore.idl.toHash(copyData.circ_summary, true);
-                    flatCopy._circ_lib = copyData.circ.circ_lib();
-                    flatCopy._duration = copyData.circ.duration();
-                }
-                flatCopy.index = service.index++;
-                service.copies.unshift(flatCopy);
-            }
-
-            //Get in-house use count
-            egCore.pcrud.search('aihu',
-                {item : flatCopy.id}, {}, {idlist : true, atomic : true})
-            .then(function(uses) {
-                flatCopy._inHouseUseCount = uses.length;
-                copyData.copy._inHouseUseCount = uses.length;
-            });
-
-            return lastRes = {
-                copy : copyData.copy,
-                index : flatCopy.index
-            }
-        });
-
-
-    }
-
-    service.add_copies_to_bucket = function(copy_list) {
-        if (copy_list.length == 0) return;
-
-        return $uibModal.open({
-            templateUrl: './cat/catalog/t_add_to_bucket',
-            animation: true,
-            size: 'md',
-            controller:
-                   ['$scope','$uibModalInstance',
-            function($scope , $uibModalInstance) {
-
-                $scope.bucket_id = 0;
-                $scope.newBucketName = '';
-                $scope.allBuckets = [];
-
-                egCore.net.request(
-                    'open-ils.actor',
-                    'open-ils.actor.container.retrieve_by_class.authoritative',
-                    egCore.auth.token(), egCore.auth.user().id(),
-                    'copy', 'staff_client'
-                ).then(function(buckets) { $scope.allBuckets = buckets; });
-
-                $scope.add_to_bucket = function() {
-                    var promises = [];
-                    angular.forEach(copy_list, function (cp) {
-                        var item = new egCore.idl.ccbi()
-                        item.bucket($scope.bucket_id);
-                        item.target_copy(cp);
-                        promises.push(
-                            egCore.net.request(
-                                'open-ils.actor',
-                                'open-ils.actor.container.item.create',
-                                egCore.auth.token(), 'copy', item
-                            )
-                        );
-
-                        return $q.all(promises).then(function() {
-                            $uibModalInstance.close();
-                        });
-                    });
-                }
-
-                $scope.add_to_new_bucket = function() {
-                    var bucket = new egCore.idl.ccb();
-                    bucket.owner(egCore.auth.user().id());
-                    bucket.name($scope.newBucketName);
-                    bucket.description('');
-                    bucket.btype('staff_client');
-
-                    return egCore.net.request(
-                        'open-ils.actor',
-                        'open-ils.actor.container.create',
-                        egCore.auth.token(), 'copy', bucket
-                    ).then(function(bucket) {
-                        $scope.bucket_id = bucket;
-                        $scope.add_to_bucket();
-                    });
-                }
-
-                $scope.cancel = function() {
-                    $uibModalInstance.dismiss();
-                }
-            }]
-        });
-    }
-
-    service.make_copies_bookable = function(items) {
-
-        var copies_by_record = {};
-        var record_list = [];
-        angular.forEach(
-            items,
-            function (item) {
-                var record_id = item['call_number.record.id'];
-                if (typeof copies_by_record[ record_id ] == 'undefined') {
-                    copies_by_record[ record_id ] = [];
-                    record_list.push( record_id );
-                }
-                copies_by_record[ record_id ].push(item.id);
-            }
-        );
-
-        var promises = [];
-        var combined_results = [];
-        angular.forEach(record_list, function(record_id) {
-            promises.push(
-                egCore.net.request(
-                    'open-ils.booking',
-                    'open-ils.booking.resources.create_from_copies',
-                    egCore.auth.token(),
-                    copies_by_record[record_id]
-                ).then(function(results) {
-                    if (results && results['brsrc']) {
-                        combined_results = combined_results.concat(results['brsrc']);
-                    }
-                })
-            );
-        });
-
-        $q.all(promises).then(function() {
-            if (combined_results.length > 0) {
-                $uibModal.open({
-                    template: '<eg-embed-frame url="booking_admin_url" handlers="funcs"></eg-embed-frame>',
-                    animation: true,
-                    size: 'md',
-                    controller:
-                           ['$scope','$location','egCore','$uibModalInstance',
-                    function($scope , $location , egCore , $uibModalInstance) {
-
-                        $scope.funcs = {
-                            ses : egCore.auth.token(),
-                            resultant_brsrc : combined_results.map(function(o) { return o[0]; })
-                        }
-
-                        var booking_path = '/eg/conify/global/booking/resource';
-
-                        $scope.booking_admin_url =
-                            $location.absUrl().replace(/\/eg\/staff.*/, booking_path);
-                    }]
-                });
-            }
-        });
-    }
-
-    service.book_copies_now = function(items) {
-        var copies_by_record = {};
-        var record_list = [];
-        angular.forEach(
-            items,
-            function (item) {
-                var record_id = item['call_number.record.id'];
-                if (typeof copies_by_record[ record_id ] == 'undefined') {
-                    copies_by_record[ record_id ] = [];
-                    record_list.push( record_id );
-                }
-                copies_by_record[ record_id ].push(item.id);
-            }
-        );
-
-        var promises = [];
-        var combined_brt = [];
-        var combined_brsrc = [];
-        angular.forEach(record_list, function(record_id) {
-            promises.push(
-                egCore.net.request(
-                    'open-ils.booking',
-                    'open-ils.booking.resources.create_from_copies',
-                    egCore.auth.token(),
-                    copies_by_record[record_id]
-                ).then(function(results) {
-                    if (results && results['brt']) {
-                        combined_brt = combined_brt.concat(results['brt']);
-                    }
-                    if (results && results['brsrc']) {
-                        combined_brsrc = combined_brsrc.concat(results['brsrc']);
-                    }
-                })
-            );
-        });
-
-        $q.all(promises).then(function() {
-            if (combined_brt.length > 0 || combined_brsrc.length > 0) {
-                $uibModal.open({
-                    template: '<eg-embed-frame url="booking_admin_url" handlers="funcs"></eg-embed-frame>',
-                    animation: true,
-                    size: 'md',
-                    controller:
-                           ['$scope','$location','egCore','$uibModalInstance',
-                    function($scope , $location , egCore , $uibModalInstance) {
-
-                        $scope.funcs = {
-                            ses : egCore.auth.token(),
-                            bresv_interface_opts : {
-                                booking_results : {
-                                     brt : combined_brt
-                                    ,brsrc : combined_brsrc
-                                }
-                            }
-                        }
-
-                        var booking_path = '/eg/booking/reservation';
-
-                        $scope.booking_admin_url =
-                            $location.absUrl().replace(/\/eg\/staff.*/, booking_path);
-
-                    }]
-                });
-            }
-        });
-    }
-
-    service.requestItems = function(copy_list) {
-        if (copy_list.length == 0) return;
-
-        return $uibModal.open({
-            templateUrl: './cat/catalog/t_request_items',
-            animation: true,
-            controller:
-                   ['$scope','$uibModalInstance','egUser',
-            function($scope , $uibModalInstance , egUser) {
-                $scope.user = null;
-                $scope.first_user_fetch = true;
-
-                $scope.hold_data = {
-                    hold_type : 'C',
-                    copy_list : copy_list,
-                    pickup_lib: egCore.org.get(egCore.auth.user().ws_ou()),
-                    user      : egCore.auth.user().id()
-                };
-
-                egUser.get( $scope.hold_data.user ).then(function(u) {
-                    $scope.user = u;
-                    $scope.barcode = u.card().barcode();
-                    $scope.user_name = egUser.format_name(u);
-                    $scope.hold_data.user = u.id();
-                });
-
-                $scope.user_name = '';
-                $scope.barcode = '';
-                $scope.$watch('barcode', function (n) {
-                    if (!$scope.first_user_fetch) {
-                        egUser.getByBarcode(n).then(function(u) {
-                            $scope.user = u;
-                            $scope.user_name = egUser.format_name(u);
-                            $scope.hold_data.user = u.id();
-                        }, function() {
-                            $scope.user = null;
-                            $scope.user_name = '';
-                            delete $scope.hold_data.user;
-                        });
-                    }
-                    $scope.first_user_fetch = false;
-                });
-
-                $scope.ok = function(h) {
-                    var args = {
-                        patronid  : h.user,
-                        hold_type : h.hold_type,
-                        pickup_lib: h.pickup_lib.id(),
-                        depth     : 0
-                    };
-
-                    egCore.net.request(
-                        'open-ils.circ',
-                        'open-ils.circ.holds.test_and_create.batch.override',
-                        egCore.auth.token(), args, h.copy_list
-                    );
-
-                    $uibModalInstance.close();
-                }
-
-                $scope.cancel = function($event) {
-                    $uibModalInstance.dismiss();
-                    $event.preventDefault();
-                }
-            }]
-        });
-    }
-
-    service.attach_to_peer_bib = function(items) {
-        if (items.length == 0) return;
-
-        egCore.hatch.getItem('eg.cat.marked_conjoined_record').then(function(target_record) {
-            if (!target_record) return;
-
-            return $uibModal.open({
-                templateUrl: './cat/catalog/t_conjoined_selector',
-                animation: true,
-                controller:
-                       ['$scope','$uibModalInstance',
-                function($scope , $uibModalInstance) {
-                    $scope.update = false;
-
-                    $scope.peer_type = null;
-                    $scope.peer_type_list = [];
-
-                    get_peer_types = function() {
-                        if (egCore.env.bpt)
-                            return $q.when(egCore.env.bpt.list);
-
-                        return egCore.pcrud.retrieveAll('bpt', null, {atomic : true})
-                        .then(function(list) {
-                            egCore.env.absorbList(list, 'bpt');
-                            return list;
-                        });
-                    }
-
-                    get_peer_types().then(function(list){
-                        $scope.peer_type_list = list;
-                    });
-
-                    $scope.ok = function(type) {
-                        var promises = [];
-
-                        angular.forEach(items, function (cp) {
-                            var n = new egCore.idl.bpbcm();
-                            n.isnew(true);
-                            n.peer_record(target_record);
-                            n.target_copy(cp.id);
-                            n.peer_type(type);
-                            promises.push(egCore.pcrud.create(n).then(function(){service.add_barcode_to_list(cp.barcode)}));
-                        });
-
-                        return $q.all(promises).then(function(){$uibModalInstance.close()});
-                    }
-
-                    $scope.cancel = function($event) {
-                        $uibModalInstance.dismiss();
-                        $event.preventDefault();
-                    }
-                }]
-            });
-        });
-    }
-
-    service.selectedHoldingsCopyDelete = function (items) {
-        if (items.length == 0) return;
-
-        var copy_objects = [];
-        egCore.pcrud.search('acp',
-            {deleted : 'f', id : items.map(function(el){return el.id;}) },
-            { flesh : 1, flesh_fields : { acp : ['call_number'] } }
-        ).then(function(copy) {
-            copy_objects.push(copy);
-        }).then(function() {
-
-            var cnHash = {};
-            var perCnCopies = {};
-
-            var cn_count = 0;
-            var cp_count = 0;
-
-            angular.forEach(
-                copy_objects,
-                function (cp) {
-                    cp.isdeleted(1);
-                    cp_count++;
-                    var cn_id = cp.call_number().id();
-                    if (!cnHash[cn_id]) {
-                        cnHash[cn_id] = cp.call_number();
-                        perCnCopies[cn_id] = [cp];
-                    } else {
-                        perCnCopies[cn_id].push(cp);
-                    }
-                    cp.call_number(cn_id); // prevent loops in JSON-ification
-                }
-            );
-
-            angular.forEach(perCnCopies, function (v, k) {
-                cnHash[k].copies(v);
-            });
-
-            cnList = [];
-            angular.forEach(cnHash, function (v, k) {
-                cnList.push(v);
-            });
-
-            if (cnList.length == 0) return;
-
-            var flags = {};
-
-            egConfirmDialog.open(
-                egCore.strings.CONFIRM_DELETE_COPIES_VOLUMES,
-                egCore.strings.CONFIRM_DELETE_COPIES_VOLUMES_MESSAGE,
-                {copies : cp_count, volumes : cn_count}
-            ).result.then(function() {
-                egCore.net.request(
-                    'open-ils.cat',
-                    'open-ils.cat.asset.volume.fleshed.batch.update.override',
-                    egCore.auth.token(), cnList, 1, flags
-                ).then(function(){
-                    angular.forEach(items, function(cp){service.add_barcode_to_list(cp.barcode)});
-                });
-            });
-        });
-    }
-
-    service.checkin = function (items) {
-        angular.forEach(items, function (cp) {
-            egCirc.checkin({copy_barcode:cp.barcode}).then(
-                function() { service.add_barcode_to_list(cp.barcode) }
-            );
-        });
-    }
-
-    service.renew = function (items) {
-        angular.forEach(items, function (cp) {
-            egCirc.renew({copy_barcode:cp.barcode}).then(
-                function() { service.add_barcode_to_list(cp.barcode) }
-            );
-        });
-    }
-
-    service.cancel_transit = function (items) {
-        angular.forEach(items, function(cp) {
-            egCirc.find_copy_transit(null, {copy_barcode:cp.barcode})
-                .then(function(t) { return egCirc.abort_transit(t.id())    })
-                .then(function()  { return service.add_barcode_to_list(cp.barcode) });
-        });
-    }
-
-    service.selectedHoldingsDamaged = function (items) {
-        egCirc.mark_damaged(items.map(function(el){return el.id;})).then(function(){
-            angular.forEach(items, function(cp){service.add_barcode_to_list(cp.barcode)});
-        });
-    }
-
-    service.selectedHoldingsMissing = function (items) {
-        egCirc.mark_missing(items.map(function(el){return el.id;})).then(function(){
-            angular.forEach(items, function(cp){service.add_barcode_to_list(cp.barcode)});
-        });
-    }
-
-    service.gatherSelectedRecordIds = function (items) {
-        var rid_list = [];
-        angular.forEach(
-            items,
-            function (item) {
-                if (rid_list.indexOf(item['call_number.record.id']) == -1)
-                    rid_list.push(item['call_number.record.id'])
-            }
-        );
-        return rid_list;
-    }
-
-    service.gatherSelectedVolumeIds = function (items,rid) {
-        var cn_id_list = [];
-        angular.forEach(
-            items,
-            function (item) {
-                if (rid && item['call_number.record.id'] != rid) return;
-                if (cn_id_list.indexOf(item['call_number.id']) == -1)
-                    cn_id_list.push(item['call_number.id'])
-            }
-        );
-        return cn_id_list;
-    }
-
-    service.gatherSelectedHoldingsIds = function (items,rid) {
-        var cp_id_list = [];
-        angular.forEach(
-            items,
-            function (item) {
-                if (rid && item['call_number.record.id'] != rid) return;
-                cp_id_list.push(item.id)
-            }
-        );
-        return cp_id_list;
-    }
-
-    service.spawnHoldingsAdd = function (items,use_vols,use_copies){
-        angular.forEach(service.gatherSelectedRecordIds(items), function (r) {
-            var raw = [];
-            if (use_copies) { // just a copy on existing volumes
-                angular.forEach(service.gatherSelectedVolumeIds(items,r), function (v) {
-                    raw.push( {callnumber : v} );
-                });
-            } else if (use_vols) {
-                angular.forEach(
-                    service.gatherSelectedHoldingsIds(items,r),
-                    function (i) {
-                        angular.forEach(items, function(item) {
-                            if (i == item.id) raw.push({owner : item['call_number.owning_lib']});
-                        });
-                    }
-                );
-            }
-
-            if (raw.length == 0) raw.push({});
-
-            egCore.net.request(
-                'open-ils.actor',
-                'open-ils.actor.anon_cache.set_value',
-                null, 'edit-these-copies', {
-                    record_id: r,
-                    raw: raw,
-                    hide_vols : false,
-                    hide_copies : false
-                }
-            ).then(function(key) {
-                if (key) {
-                    var url = egCore.env.basePath + 'cat/volcopy/' + key;
-                    $timeout(function() { $window.open(url, '_blank') });
-                } else {
-                    alert('Could not create anonymous cache key!');
-                }
-            });
-        });
-    }
-
-    service.spawnHoldingsEdit = function (items,hide_vols,hide_copies){
-        angular.forEach(service.gatherSelectedRecordIds(items), function (r) {
-            egCore.net.request(
-                'open-ils.actor',
-                'open-ils.actor.anon_cache.set_value',
-                null, 'edit-these-copies', {
-                    record_id: r,
-                    copies: service.gatherSelectedHoldingsIds(items,r),
-                    raw: {},
-                    hide_vols : hide_vols,
-                    hide_copies : hide_copies
-                }
-            ).then(function(key) {
-                if (key) {
-                    var url = egCore.env.basePath + 'cat/volcopy/' + key;
-                    $timeout(function() { $window.open(url, '_blank') });
-                } else {
-                    alert('Could not create anonymous cache key!');
-                }
-            });
-        });
-    }
-
-    service.replaceBarcodes = function(items) {
-        angular.forEach(items, function (cp) {
-            $uibModal.open({
-                templateUrl: './cat/share/t_replace_barcode',
-                animation: true,
-                controller:
-                           ['$scope','$uibModalInstance',
-                    function($scope , $uibModalInstance) {
-                        $scope.isModal = true;
-                        $scope.focusBarcode = false;
-                        $scope.focusBarcode2 = true;
-                        $scope.barcode1 = cp.barcode;
-
-                        $scope.updateBarcode = function() {
-                            $scope.copyNotFound = false;
-                            $scope.updateOK = false;
-
-                            egCore.pcrud.search('acp',
-                                {deleted : 'f', barcode : $scope.barcode1})
-                            .then(function(copy) {
-
-                                if (!copy) {
-                                    $scope.focusBarcode = true;
-                                    $scope.copyNotFound = true;
-                                    return;
-                                }
-
-                                $scope.copyId = copy.id();
-                                copy.barcode($scope.barcode2);
-
-                                egCore.pcrud.update(copy).then(function(stat) {
-                                    $scope.updateOK = stat;
-                                    $scope.focusBarcode = true;
-                                    if (stat) service.add_barcode_to_list(copy.barcode());
-                                });
-
-                            });
-                            $uibModalInstance.close();
-                        }
-
-                        $scope.cancel = function($event) {
-                            $uibModalInstance.dismiss();
-                            $event.preventDefault();
-                        }
-                    }
-                ]
-            });
-        });
-    }
-
-    // this "transfers" selected copies to a new owning library,
-    // auto-creating volumes and deleting unused volumes as required.
-    service.changeItemOwningLib = function(items) {
-        var xfer_target = egCore.hatch.getLocalItem('eg.cat.volume_transfer_target');
-        if (!xfer_target || !items.length) {
-            return;
-        }
-        var vols_to_move   = {};
-        var copies_to_move = {};
-        angular.forEach(items, function(item) {
-            if (item['call_number.owning_lib'] != xfer_target) {
-                if (item['call_number.id'] in vols_to_move) {
-                    copies_to_move[item['call_number.id']].push(item.id);
-                } else {
-                    vols_to_move[item['call_number.id']] = {
-                        label       : item['call_number.label'],
-                        label_class : item['call_number.label_class'],
-                        record      : item['call_number.record.id'],
-                        prefix      : item['call_number.prefix.id'],
-                        suffix      : item['call_number.suffix.id']
-                    };
-                    copies_to_move[item['call_number.id']] = new Array;
-                    copies_to_move[item['call_number.id']].push(item.id);
-                }
-            }
-        });
-
-        var promises = [];
-        angular.forEach(vols_to_move, function(vol) {
-            promises.push(egCore.net.request(
-                'open-ils.cat',
-                'open-ils.cat.call_number.find_or_create',
-                egCore.auth.token(),
-                vol.label,
-                vol.record,
-                xfer_target,
-                vol.prefix,
-                vol.suffix,
-                vol.label_class
-            ).then(function(resp) {
-                var evt = egCore.evt.parse(resp);
-                if (evt) return;
-                return egCore.net.request(
-                    'open-ils.cat',
-                    'open-ils.cat.transfer_copies_to_volume',
-                    egCore.auth.token(),
-                    resp.acn_id,
-                    copies_to_move[vol.id]
-                );
-            }));
-        });
-
-        angular.forEach(
-            items,
-            function(cp){
-                promises.push(
-                    function(){ service.add_barcode_to_list(cp.barcode) }
-                )
-            }
-        );
-        $q.all(promises);
-    }
-
-    service.transferItems = function (items){
-        var xfer_target = egCore.hatch.getLocalItem('eg.cat.item_transfer_target');
-        var copy_ids = service.gatherSelectedHoldingsIds(items);
-        if (xfer_target && copy_ids.length > 0) {
-            egCore.net.request(
-                'open-ils.cat',
-                'open-ils.cat.transfer_copies_to_volume',
-                egCore.auth.token(),
-                xfer_target,
-                copy_ids
-            ).then(
-                function(resp) { // oncomplete
-                    var evt = egCore.evt.parse(resp);
-                    if (evt) {
-                        egConfirmDialog.open(
-                            egCore.strings.OVERRIDE_TRANSFER_COPIES_TO_MARKED_VOLUME_TITLE,
-                            egCore.strings.OVERRIDE_TRANSFER_COPIES_TO_MARKED_VOLUME_BODY,
-                            {'evt_desc': evt}
-                        ).result.then(function() {
-                            egCore.net.request(
-                                'open-ils.cat',
-                                'open-ils.cat.transfer_copies_to_volume.override',
-                                egCore.auth.token(),
-                                xfer_target,
-                                copy_ids,
-                                { events: ['TITLE_LAST_COPY', 'COPY_DELETE_WARNING'] }
-                            );
-                        }).then(function() {
-                            angular.forEach(items, function(cp){service.add_barcode_to_list(cp.barcode)});
-                        });
-                    } else {
-                        angular.forEach(items, function(cp){service.add_barcode_to_list(cp.barcode)});
-                    }
-
-                },
-                null, // onerror
-                null // onprogress
-            );
-        }
-    }
-
-    service.mark_missing_pieces = function(copy) {
-        var b = copy.barcode();
-        var t = egCore.idl.toHash(copy.call_number()).record.title;
-        egConfirmDialog.open(
-            egCore.strings.CONFIRM_MARK_MISSING_TITLE,
-            egCore.strings.CONFIRM_MARK_MISSING_BODY,
-            { barcode : b, title : t }
-        ).result.then(function() {
-
-            // kick off mark missing
-            return egCore.net.request(
-                'open-ils.circ',
-                'open-ils.circ.mark_item_missing_pieces',
-                egCore.auth.token(), copy.id()
-            )
-
-        }).then(function(resp) {
-            var evt = egCore.evt.parse(resp); // should always produce event
-
-            if (evt.textcode == 'ACTION_CIRCULATION_NOT_FOUND') {
-                return egAlertDialog.open(
-                    egCore.strings.CIRC_NOT_FOUND, {barcode : copy.barcode()});
-            }
-
-            var payload = evt.payload;
-
-            // TODO: open copy editor inline?  new tab?
-
-            // print the missing pieces slip
-            var promise = $q.when();
-            if (payload.slip) {
-                // wait for completion, since it may spawn a confirm dialog
-                promise = egCore.print.print({
-                    context : 'default',
-                    content_type : 'text/html',
-                    content : payload.slip.template_output().data()
-                });
-            }
-
-            if (payload.letter) {
-                $scope.letter = payload.letter.template_output().data();
-            }
-
-            // apply patron penalty
-            if (payload.circ) {
-                promise.then(function() {
-                    egCirc.create_penalty(payload.circ.usr())
-                });
-            }
-
-        });
-    }
-
-    return service;
-}])
-
 /**
  * Search bar along the top of the page.
  * Parent scope for list and detail views
  */
 .controller('SearchCtrl', 
-       ['$scope','$location','$timeout','egCore','egGridDataProvider','itemSvc',
+       ['$scope','$location','$timeout','egCore','egGridDataProvider','egItem',
 function($scope , $location , $timeout , egCore , egGridDataProvider , itemSvc) {
     $scope.args = {}; // search args
 
@@ -1120,7 +233,7 @@ function($scope , $location , $timeout , egCore , egGridDataProvider , itemSvc)
  * List view - grid stuff
  */
 .controller('ListCtrl', 
-       ['$scope','$q','$routeParams','$location','$timeout','$window','egCore','egGridDataProvider','itemSvc','egUser','$uibModal','egCirc','egConfirmDialog',
+       ['$scope','$q','$routeParams','$location','$timeout','$window','egCore','egGridDataProvider','egItem','egUser','$uibModal','egCirc','egConfirmDialog',
 function($scope , $q , $routeParams , $location , $timeout , $window , egCore , egGridDataProvider , itemSvc , egUser , $uibModal , egCirc , egConfirmDialog) {
     var copyId = [];
     var cp_list = $routeParams.idList;
@@ -1397,7 +510,7 @@ function($scope , $q , $routeParams , $location , $timeout , $window , egCore ,
  * Detail view -- shows one copy
  */
 .controller('ViewCtrl', 
-       ['$scope','$q','$location','$routeParams','$timeout','$window','egCore','itemSvc','egBilling',
+       ['$scope','$q','$location','$routeParams','$timeout','$window','egCore','egItem','egBilling',
 function($scope , $q , $location , $routeParams , $timeout , $window , egCore , itemSvc , egBilling) {
     var copyId = $routeParams.id;
     $scope.args.copyId = copyId;
diff --git a/Open-ILS/web/js/ui/default/staff/cat/item/missing_pieces.js b/Open-ILS/web/js/ui/default/staff/cat/item/missing_pieces.js
index 06fad21..a734e4a 100644
--- a/Open-ILS/web/js/ui/default/staff/cat/item/missing_pieces.js
+++ b/Open-ILS/web/js/ui/default/staff/cat/item/missing_pieces.js
@@ -2,8 +2,8 @@ angular.module('egItemMissingPieces',
     ['ngRoute', 'ui.bootstrap', 'egCoreMod','egUiMod'])
 
 .controller('MissingPiecesCtrl',
-       ['$scope','$q','$window','$location','egCore','egConfirmDialog','egAlertDialog','egCirc',
-function($scope , $q , $window , $location , egCore , egConfirmDialog , egAlertDialog , egCirc) {
+       ['$scope','$q','$window','$location','egCore','egConfirmDialog','egAlertDialog','egCirc','egItem',
+function($scope, $q, $window, $location, egCore, egConfirmDialog, egAlertDialog, egCirc, itemSvc) {
     
     $scope.selectMe = true; // focus text input
     $scope.args = {};
@@ -46,57 +46,7 @@ function($scope , $q , $window , $location , egCore , egConfirmDialog , egAlertD
     }
 
     function mark_missing_pieces(copy) {
-
-        egConfirmDialog.open(
-            egCore.strings.CONFIRM_MARK_MISSING_TITLE,
-            egCore.strings.CONFIRM_MARK_MISSING_BODY, {
-            barcode : copy.barcode(), 
-            title : copy.call_number().record().simple_record().title()
-
-        }).result.then(function() {
-
-            // kick off mark missing
-            return egCore.net.request(
-                'open-ils.circ',
-                'open-ils.circ.mark_item_missing_pieces',
-                egCore.auth.token(), copy.id()
-            )
-
-        }).then(function(resp) {
-            var evt = egCore.evt.parse(resp); // should always produce event
-
-            if (evt.textcode == 'ACTION_CIRCULATION_NOT_FOUND') {
-                return egAlertDialog.open(
-                    egCore.strings.CIRC_NOT_FOUND, {barcode : copy.barcode()});
-            }
-
-            var payload = evt.payload;
-
-            // TODO: open copy editor inline?  new tab?
-
-            // print the missing pieces slip
-            var promise = $q.when();
-            if (payload.slip) {
-                // wait for completion, since it may spawn a confirm dialog
-                promise = egCore.print.print({
-                    context : 'default', 
-                    content_type : 'text/html',
-                    content : payload.slip.template_output().data()
-                });
-            }
-
-            if (payload.letter) {
-                $scope.letter = payload.letter.template_output().data();
-            }
-
-            // apply patron penalty
-            if (payload.circ) {
-                promise.then(function() {
-                    egCirc.create_penalty(payload.circ.usr())
-                });
-            }  
-
-        });
+        itemSvc.mark_missing_pieces(copy);
     }
 
     $scope.print_letter = function() {
diff --git a/Open-ILS/web/js/ui/default/staff/circ/checkin/app.js b/Open-ILS/web/js/ui/default/staff/circ/checkin/app.js
index 8f2fb50..e3bf69a 100644
--- a/Open-ILS/web/js/ui/default/staff/circ/checkin/app.js
+++ b/Open-ILS/web/js/ui/default/staff/circ/checkin/app.js
@@ -1,5 +1,5 @@
 angular.module('egCheckinApp', ['ngRoute', 'ui.bootstrap', 
-    'egCoreMod', 'egUiMod', 'egGridMod', 'egUserMod', 'egItemStatus'])
+    'egCoreMod', 'egUiMod', 'egGridMod', 'egUserMod'])
 
 .config(function($routeProvider, $locationProvider, $compileProvider) {
     $locationProvider.html5Mode(true);
@@ -34,7 +34,7 @@ angular.module('egCheckinApp', ['ngRoute', 'ui.bootstrap',
  * Manages checkin
  */
 .controller('CheckinCtrl',
-       ['$scope','$q','$window','$location', '$timeout','egCore','checkinSvc','egGridDataProvider','egCirc', 'itemSvc',
+       ['$scope','$q','$window','$location', '$timeout','egCore','checkinSvc','egGridDataProvider','egCirc', 'egItem',
 function($scope , $q , $window , $location , $timeout , egCore , checkinSvc , egGridDataProvider , egCirc, itemSvc)  {
 
     $scope.focusMe = true;
diff --git a/Open-ILS/web/js/ui/default/staff/circ/services/item.js b/Open-ILS/web/js/ui/default/staff/circ/services/item.js
new file mode 100644
index 0000000..3c0953f
--- /dev/null
+++ b/Open-ILS/web/js/ui/default/staff/circ/services/item.js
@@ -0,0 +1,891 @@
+/**
+ * Shared item services for circulation
+ */
+
+angular.module('egCoreMod')
+    .factory('egItem',
+       ['egCore','egCirc','$uibModal','$q','$timeout','$window','egConfirmDialog','egAlertDialog',
+function(egCore , egCirc , $uibModal , $q , $timeout , $window , egConfirmDialog , egAlertDialog ) {
+
+    var service = {
+        copies : [], // copy barcode search results
+        index : 0 // search grid index
+    };
+
+    service.flesh = {   
+        flesh : 3, 
+        flesh_fields : {
+            acp : ['call_number','location','status','location','floating','circ_modifier',
+                'age_protect','circ_lib'],
+            acn : ['record','prefix','suffix','label_class'],
+            bre : ['simple_record','creator','editor']
+        },
+        select : { 
+            // avoid fleshing MARC on the bre
+            // note: don't add simple_record.. not sure why
+            bre : ['id','tcn_value','creator','editor'],
+        } 
+    }
+
+    service.circFlesh = {
+        flesh : 2,
+        flesh_fields : {
+            circ : [
+                'usr',
+                'workstation',
+                'checkin_workstation',
+                'checkin_lib',
+                'duration_rule',
+                'max_fine_rule',
+                'recurring_fine_rule'
+            ],
+            au : ['card']
+        },
+        order_by : {circ : 'xact_start desc'},
+        limit :  1
+    }
+
+    //Retrieve separate copy, aacs, and accs information
+    service.getCopy = function(barcode, id) {
+        if (barcode) {
+            // handle barcode completion
+            return egCirc.handle_barcode_completion(barcode)
+            .then(function(actual_barcode) {
+                return egCore.pcrud.search(
+                    'acp', {barcode : actual_barcode, deleted : 'f'},
+                    service.flesh).then(function(copy) {return copy});
+            });
+        }
+
+        return egCore.pcrud.retrieve( 'acp', id, service.flesh)
+            .then(function(copy) {return copy});
+    }
+
+    service.getCirc = function(id) {
+        return egCore.pcrud.search('aacs', { target_copy : id },
+            service.circFlesh).then(function(circ) {return circ});
+    }
+
+    service.getSummary = function(id) {
+        return circ_summary = egCore.net.request(
+            'open-ils.circ',
+            'open-ils.circ.renewal_chain.retrieve_by_circ.summary',
+            egCore.auth.token(), id).then(
+                function(circ_summary) {return circ_summary});
+    }
+
+    //Combine copy, circ, and accs information
+    service.retrieveCopyData = function(barcode, id) {
+        var copyData = {};
+
+        var fetchCopy = function(barcode, id) {
+            return service.getCopy(barcode, id)
+                .then(function(copy) {
+                    copyData.copy = copy;
+                    return copyData;
+                });
+        }
+        var fetchCirc = function(copy) {
+            return service.getCirc(copy.id())
+                .then(function(circ) {
+                    copyData.circ = circ;
+                    return copyData;
+                });
+        }
+        var fetchSummary = function(circ) {
+            return service.getSummary(circ.id())
+                .then(function(summary) {
+                    copyData.circ_summary = summary;
+                    return copyData;
+                });
+        }
+        return fetchCopy(barcode, id).then(function(res) {
+            return fetchCirc(copyData.copy).then(function(res) {
+                if (copyData.circ) {
+                    return fetchSummary(copyData.circ).then(function() {
+                        return copyData;
+                    });
+                } else {
+                    return copyData;
+                }
+            });
+        });
+
+    }
+
+    // resolved with the last received copy
+    service.fetch = function(barcode, id, noListDupes) {
+        var copy;
+        var circ;
+        var circ_summary;
+        var lastRes = {};
+
+        return service.retrieveCopyData(barcode, id)
+        .then(function(copyData) {
+            //Make sure we're getting a completed copyData - no plain acp or circ objects
+            if (copyData.circ) {
+                // flesh circ_lib locally
+                copyData.circ.circ_lib(egCore.org.get(copyData.circ.circ_lib()));
+                copyData.circ.checkin_workstation(
+                    egCore.org.get(copyData.circ.checkin_workstation()));
+            }
+            var flatCopy;
+
+            if (noListDupes) {
+                // use the existing copy if possible
+                flatCopy = service.copies.filter(
+                    function(c) {return c.id == copyData.copy.id()})[0];
+            }
+
+            if (!flatCopy) {
+                flatCopy = egCore.idl.toHash(copyData.copy, true);
+
+                if (copyData.circ) {
+                    flatCopy._circ = egCore.idl.toHash(copyData.circ, true);
+                    flatCopy._circ_summary = egCore.idl.toHash(copyData.circ_summary, true);
+                    flatCopy._circ_lib = copyData.circ.circ_lib();
+                    flatCopy._duration = copyData.circ.duration();
+                }
+                flatCopy.index = service.index++;
+                service.copies.unshift(flatCopy);
+            }
+
+            //Get in-house use count
+            egCore.pcrud.search('aihu',
+                {item : flatCopy.id}, {}, {idlist : true, atomic : true})
+            .then(function(uses) {
+                flatCopy._inHouseUseCount = uses.length;
+                copyData.copy._inHouseUseCount = uses.length;
+            });
+
+            return lastRes = {
+                copy : copyData.copy,
+                index : flatCopy.index
+            }
+        });
+
+
+    }
+
+    service.add_copies_to_bucket = function(copy_list) {
+        if (copy_list.length == 0) return;
+
+        return $uibModal.open({
+            templateUrl: './cat/catalog/t_add_to_bucket',
+            animation: true,
+            size: 'md',
+            controller:
+                   ['$scope','$uibModalInstance',
+            function($scope , $uibModalInstance) {
+
+                $scope.bucket_id = 0;
+                $scope.newBucketName = '';
+                $scope.allBuckets = [];
+
+                egCore.net.request(
+                    'open-ils.actor',
+                    'open-ils.actor.container.retrieve_by_class.authoritative',
+                    egCore.auth.token(), egCore.auth.user().id(),
+                    'copy', 'staff_client'
+                ).then(function(buckets) { $scope.allBuckets = buckets; });
+
+                $scope.add_to_bucket = function() {
+                    var promises = [];
+                    angular.forEach(copy_list, function (cp) {
+                        var item = new egCore.idl.ccbi()
+                        item.bucket($scope.bucket_id);
+                        item.target_copy(cp);
+                        promises.push(
+                            egCore.net.request(
+                                'open-ils.actor',
+                                'open-ils.actor.container.item.create',
+                                egCore.auth.token(), 'copy', item
+                            )
+                        );
+
+                        return $q.all(promises).then(function() {
+                            $uibModalInstance.close();
+                        });
+                    });
+                }
+
+                $scope.add_to_new_bucket = function() {
+                    var bucket = new egCore.idl.ccb();
+                    bucket.owner(egCore.auth.user().id());
+                    bucket.name($scope.newBucketName);
+                    bucket.description('');
+                    bucket.btype('staff_client');
+
+                    return egCore.net.request(
+                        'open-ils.actor',
+                        'open-ils.actor.container.create',
+                        egCore.auth.token(), 'copy', bucket
+                    ).then(function(bucket) {
+                        $scope.bucket_id = bucket;
+                        $scope.add_to_bucket();
+                    });
+                }
+
+                $scope.cancel = function() {
+                    $uibModalInstance.dismiss();
+                }
+            }]
+        });
+    }
+
+    service.make_copies_bookable = function(items) {
+
+        var copies_by_record = {};
+        var record_list = [];
+        angular.forEach(
+            items,
+            function (item) {
+                var record_id = item['call_number.record.id'];
+                if (typeof copies_by_record[ record_id ] == 'undefined') {
+                    copies_by_record[ record_id ] = [];
+                    record_list.push( record_id );
+                }
+                copies_by_record[ record_id ].push(item.id);
+            }
+        );
+
+        var promises = [];
+        var combined_results = [];
+        angular.forEach(record_list, function(record_id) {
+            promises.push(
+                egCore.net.request(
+                    'open-ils.booking',
+                    'open-ils.booking.resources.create_from_copies',
+                    egCore.auth.token(),
+                    copies_by_record[record_id]
+                ).then(function(results) {
+                    if (results && results['brsrc']) {
+                        combined_results = combined_results.concat(results['brsrc']);
+                    }
+                })
+            );
+        });
+
+        $q.all(promises).then(function() {
+            if (combined_results.length > 0) {
+                $uibModal.open({
+                    template: '<eg-embed-frame url="booking_admin_url" handlers="funcs"></eg-embed-frame>',
+                    animation: true,
+                    size: 'md',
+                    controller:
+                           ['$scope','$location','egCore','$uibModalInstance',
+                    function($scope , $location , egCore , $uibModalInstance) {
+
+                        $scope.funcs = {
+                            ses : egCore.auth.token(),
+                            resultant_brsrc : combined_results.map(function(o) { return o[0]; })
+                        }
+
+                        var booking_path = '/eg/conify/global/booking/resource';
+
+                        $scope.booking_admin_url =
+                            $location.absUrl().replace(/\/eg\/staff.*/, booking_path);
+                    }]
+                });
+            }
+        });
+    }
+
+    service.book_copies_now = function(items) {
+        var copies_by_record = {};
+        var record_list = [];
+        angular.forEach(
+            items,
+            function (item) {
+                var record_id = item['call_number.record.id'];
+                if (typeof copies_by_record[ record_id ] == 'undefined') {
+                    copies_by_record[ record_id ] = [];
+                    record_list.push( record_id );
+                }
+                copies_by_record[ record_id ].push(item.id);
+            }
+        );
+
+        var promises = [];
+        var combined_brt = [];
+        var combined_brsrc = [];
+        angular.forEach(record_list, function(record_id) {
+            promises.push(
+                egCore.net.request(
+                    'open-ils.booking',
+                    'open-ils.booking.resources.create_from_copies',
+                    egCore.auth.token(),
+                    copies_by_record[record_id]
+                ).then(function(results) {
+                    if (results && results['brt']) {
+                        combined_brt = combined_brt.concat(results['brt']);
+                    }
+                    if (results && results['brsrc']) {
+                        combined_brsrc = combined_brsrc.concat(results['brsrc']);
+                    }
+                })
+            );
+        });
+
+        $q.all(promises).then(function() {
+            if (combined_brt.length > 0 || combined_brsrc.length > 0) {
+                $uibModal.open({
+                    template: '<eg-embed-frame url="booking_admin_url" handlers="funcs"></eg-embed-frame>',
+                    animation: true,
+                    size: 'md',
+                    controller:
+                           ['$scope','$location','egCore','$uibModalInstance',
+                    function($scope , $location , egCore , $uibModalInstance) {
+
+                        $scope.funcs = {
+                            ses : egCore.auth.token(),
+                            bresv_interface_opts : {
+                                booking_results : {
+                                     brt : combined_brt
+                                    ,brsrc : combined_brsrc
+                                }
+                            }
+                        }
+
+                        var booking_path = '/eg/booking/reservation';
+
+                        $scope.booking_admin_url =
+                            $location.absUrl().replace(/\/eg\/staff.*/, booking_path);
+
+                    }]
+                });
+            }
+        });
+    }
+
+    service.requestItems = function(copy_list) {
+        if (copy_list.length == 0) return;
+
+        return $uibModal.open({
+            templateUrl: './cat/catalog/t_request_items',
+            animation: true,
+            controller:
+                   ['$scope','$uibModalInstance','egUser',
+            function($scope , $uibModalInstance , egUser) {
+                $scope.user = null;
+                $scope.first_user_fetch = true;
+
+                $scope.hold_data = {
+                    hold_type : 'C',
+                    copy_list : copy_list,
+                    pickup_lib: egCore.org.get(egCore.auth.user().ws_ou()),
+                    user      : egCore.auth.user().id()
+                };
+
+                egUser.get( $scope.hold_data.user ).then(function(u) {
+                    $scope.user = u;
+                    $scope.barcode = u.card().barcode();
+                    $scope.user_name = egUser.format_name(u);
+                    $scope.hold_data.user = u.id();
+                });
+
+                $scope.user_name = '';
+                $scope.barcode = '';
+                $scope.$watch('barcode', function (n) {
+                    if (!$scope.first_user_fetch) {
+                        egUser.getByBarcode(n).then(function(u) {
+                            $scope.user = u;
+                            $scope.user_name = egUser.format_name(u);
+                            $scope.hold_data.user = u.id();
+                        }, function() {
+                            $scope.user = null;
+                            $scope.user_name = '';
+                            delete $scope.hold_data.user;
+                        });
+                    }
+                    $scope.first_user_fetch = false;
+                });
+
+                $scope.ok = function(h) {
+                    var args = {
+                        patronid  : h.user,
+                        hold_type : h.hold_type,
+                        pickup_lib: h.pickup_lib.id(),
+                        depth     : 0
+                    };
+
+                    egCore.net.request(
+                        'open-ils.circ',
+                        'open-ils.circ.holds.test_and_create.batch.override',
+                        egCore.auth.token(), args, h.copy_list
+                    );
+
+                    $uibModalInstance.close();
+                }
+
+                $scope.cancel = function($event) {
+                    $uibModalInstance.dismiss();
+                    $event.preventDefault();
+                }
+            }]
+        });
+    }
+
+    service.attach_to_peer_bib = function(items) {
+        if (items.length == 0) return;
+
+        egCore.hatch.getItem('eg.cat.marked_conjoined_record').then(function(target_record) {
+            if (!target_record) return;
+
+            return $uibModal.open({
+                templateUrl: './cat/catalog/t_conjoined_selector',
+                animation: true,
+                controller:
+                       ['$scope','$uibModalInstance',
+                function($scope , $uibModalInstance) {
+                    $scope.update = false;
+
+                    $scope.peer_type = null;
+                    $scope.peer_type_list = [];
+
+                    get_peer_types = function() {
+                        if (egCore.env.bpt)
+                            return $q.when(egCore.env.bpt.list);
+
+                        return egCore.pcrud.retrieveAll('bpt', null, {atomic : true})
+                        .then(function(list) {
+                            egCore.env.absorbList(list, 'bpt');
+                            return list;
+                        });
+                    }
+
+                    get_peer_types().then(function(list){
+                        $scope.peer_type_list = list;
+                    });
+
+                    $scope.ok = function(type) {
+                        var promises = [];
+
+                        angular.forEach(items, function (cp) {
+                            var n = new egCore.idl.bpbcm();
+                            n.isnew(true);
+                            n.peer_record(target_record);
+                            n.target_copy(cp.id);
+                            n.peer_type(type);
+                            promises.push(egCore.pcrud.create(n).then(function(){service.add_barcode_to_list(cp.barcode)}));
+                        });
+
+                        return $q.all(promises).then(function(){$uibModalInstance.close()});
+                    }
+
+                    $scope.cancel = function($event) {
+                        $uibModalInstance.dismiss();
+                        $event.preventDefault();
+                    }
+                }]
+            });
+        });
+    }
+
+    service.selectedHoldingsCopyDelete = function (items) {
+        if (items.length == 0) return;
+
+        var copy_objects = [];
+        egCore.pcrud.search('acp',
+            {deleted : 'f', id : items.map(function(el){return el.id;}) },
+            { flesh : 1, flesh_fields : { acp : ['call_number'] } }
+        ).then(function(copy) {
+            copy_objects.push(copy);
+        }).then(function() {
+
+            var cnHash = {};
+            var perCnCopies = {};
+
+            var cn_count = 0;
+            var cp_count = 0;
+
+            angular.forEach(
+                copy_objects,
+                function (cp) {
+                    cp.isdeleted(1);
+                    cp_count++;
+                    var cn_id = cp.call_number().id();
+                    if (!cnHash[cn_id]) {
+                        cnHash[cn_id] = cp.call_number();
+                        perCnCopies[cn_id] = [cp];
+                    } else {
+                        perCnCopies[cn_id].push(cp);
+                    }
+                    cp.call_number(cn_id); // prevent loops in JSON-ification
+                }
+            );
+
+            angular.forEach(perCnCopies, function (v, k) {
+                cnHash[k].copies(v);
+            });
+
+            cnList = [];
+            angular.forEach(cnHash, function (v, k) {
+                cnList.push(v);
+            });
+
+            if (cnList.length == 0) return;
+
+            var flags = {};
+
+            egConfirmDialog.open(
+                egCore.strings.CONFIRM_DELETE_COPIES_VOLUMES,
+                egCore.strings.CONFIRM_DELETE_COPIES_VOLUMES_MESSAGE,
+                {copies : cp_count, volumes : cn_count}
+            ).result.then(function() {
+                egCore.net.request(
+                    'open-ils.cat',
+                    'open-ils.cat.asset.volume.fleshed.batch.update.override',
+                    egCore.auth.token(), cnList, 1, flags
+                ).then(function(){
+                    angular.forEach(items, function(cp){service.add_barcode_to_list(cp.barcode)});
+                });
+            });
+        });
+    }
+
+    service.checkin = function (items) {
+        angular.forEach(items, function (cp) {
+            egCirc.checkin({copy_barcode:cp.barcode}).then(
+                function() { service.add_barcode_to_list(cp.barcode) }
+            );
+        });
+    }
+
+    service.renew = function (items) {
+        angular.forEach(items, function (cp) {
+            egCirc.renew({copy_barcode:cp.barcode}).then(
+                function() { service.add_barcode_to_list(cp.barcode) }
+            );
+        });
+    }
+
+    service.cancel_transit = function (items) {
+        angular.forEach(items, function(cp) {
+            egCirc.find_copy_transit(null, {copy_barcode:cp.barcode})
+                .then(function(t) { return egCirc.abort_transit(t.id())    })
+                .then(function()  { return service.add_barcode_to_list(cp.barcode) });
+        });
+    }
+
+    service.selectedHoldingsDamaged = function (items) {
+        egCirc.mark_damaged(items.map(function(el){return el.id;})).then(function(){
+            angular.forEach(items, function(cp){service.add_barcode_to_list(cp.barcode)});
+        });
+    }
+
+    service.selectedHoldingsMissing = function (items) {
+        egCirc.mark_missing(items.map(function(el){return el.id;})).then(function(){
+            angular.forEach(items, function(cp){service.add_barcode_to_list(cp.barcode)});
+        });
+    }
+
+    service.gatherSelectedRecordIds = function (items) {
+        var rid_list = [];
+        angular.forEach(
+            items,
+            function (item) {
+                if (rid_list.indexOf(item['call_number.record.id']) == -1)
+                    rid_list.push(item['call_number.record.id'])
+            }
+        );
+        return rid_list;
+    }
+
+    service.gatherSelectedVolumeIds = function (items,rid) {
+        var cn_id_list = [];
+        angular.forEach(
+            items,
+            function (item) {
+                if (rid && item['call_number.record.id'] != rid) return;
+                if (cn_id_list.indexOf(item['call_number.id']) == -1)
+                    cn_id_list.push(item['call_number.id'])
+            }
+        );
+        return cn_id_list;
+    }
+
+    service.gatherSelectedHoldingsIds = function (items,rid) {
+        var cp_id_list = [];
+        angular.forEach(
+            items,
+            function (item) {
+                if (rid && item['call_number.record.id'] != rid) return;
+                cp_id_list.push(item.id)
+            }
+        );
+        return cp_id_list;
+    }
+
+    service.spawnHoldingsAdd = function (items,use_vols,use_copies){
+        angular.forEach(service.gatherSelectedRecordIds(items), function (r) {
+            var raw = [];
+            if (use_copies) { // just a copy on existing volumes
+                angular.forEach(service.gatherSelectedVolumeIds(items,r), function (v) {
+                    raw.push( {callnumber : v} );
+                });
+            } else if (use_vols) {
+                angular.forEach(
+                    service.gatherSelectedHoldingsIds(items,r),
+                    function (i) {
+                        angular.forEach(items, function(item) {
+                            if (i == item.id) raw.push({owner : item['call_number.owning_lib']});
+                        });
+                    }
+                );
+            }
+
+            if (raw.length == 0) raw.push({});
+
+            egCore.net.request(
+                'open-ils.actor',
+                'open-ils.actor.anon_cache.set_value',
+                null, 'edit-these-copies', {
+                    record_id: r,
+                    raw: raw,
+                    hide_vols : false,
+                    hide_copies : false
+                }
+            ).then(function(key) {
+                if (key) {
+                    var url = egCore.env.basePath + 'cat/volcopy/' + key;
+                    $timeout(function() { $window.open(url, '_blank') });
+                } else {
+                    alert('Could not create anonymous cache key!');
+                }
+            });
+        });
+    }
+
+    service.spawnHoldingsEdit = function (items,hide_vols,hide_copies){
+        angular.forEach(service.gatherSelectedRecordIds(items), function (r) {
+            egCore.net.request(
+                'open-ils.actor',
+                'open-ils.actor.anon_cache.set_value',
+                null, 'edit-these-copies', {
+                    record_id: r,
+                    copies: service.gatherSelectedHoldingsIds(items,r),
+                    raw: {},
+                    hide_vols : hide_vols,
+                    hide_copies : hide_copies
+                }
+            ).then(function(key) {
+                if (key) {
+                    var url = egCore.env.basePath + 'cat/volcopy/' + key;
+                    $timeout(function() { $window.open(url, '_blank') });
+                } else {
+                    alert('Could not create anonymous cache key!');
+                }
+            });
+        });
+    }
+
+    service.replaceBarcodes = function(items) {
+        angular.forEach(items, function (cp) {
+            $uibModal.open({
+                templateUrl: './cat/share/t_replace_barcode',
+                animation: true,
+                controller:
+                           ['$scope','$uibModalInstance',
+                    function($scope , $uibModalInstance) {
+                        $scope.isModal = true;
+                        $scope.focusBarcode = false;
+                        $scope.focusBarcode2 = true;
+                        $scope.barcode1 = cp.barcode;
+
+                        $scope.updateBarcode = function() {
+                            $scope.copyNotFound = false;
+                            $scope.updateOK = false;
+
+                            egCore.pcrud.search('acp',
+                                {deleted : 'f', barcode : $scope.barcode1})
+                            .then(function(copy) {
+
+                                if (!copy) {
+                                    $scope.focusBarcode = true;
+                                    $scope.copyNotFound = true;
+                                    return;
+                                }
+
+                                $scope.copyId = copy.id();
+                                copy.barcode($scope.barcode2);
+
+                                egCore.pcrud.update(copy).then(function(stat) {
+                                    $scope.updateOK = stat;
+                                    $scope.focusBarcode = true;
+                                    if (stat) service.add_barcode_to_list(copy.barcode());
+                                });
+
+                            });
+                            $uibModalInstance.close();
+                        }
+
+                        $scope.cancel = function($event) {
+                            $uibModalInstance.dismiss();
+                            $event.preventDefault();
+                        }
+                    }
+                ]
+            });
+        });
+    }
+
+    // this "transfers" selected copies to a new owning library,
+    // auto-creating volumes and deleting unused volumes as required.
+    service.changeItemOwningLib = function(items) {
+        var xfer_target = egCore.hatch.getLocalItem('eg.cat.volume_transfer_target');
+        if (!xfer_target || !items.length) {
+            return;
+        }
+        var vols_to_move   = {};
+        var copies_to_move = {};
+        angular.forEach(items, function(item) {
+            if (item['call_number.owning_lib'] != xfer_target) {
+                if (item['call_number.id'] in vols_to_move) {
+                    copies_to_move[item['call_number.id']].push(item.id);
+                } else {
+                    vols_to_move[item['call_number.id']] = {
+                        label       : item['call_number.label'],
+                        label_class : item['call_number.label_class'],
+                        record      : item['call_number.record.id'],
+                        prefix      : item['call_number.prefix.id'],
+                        suffix      : item['call_number.suffix.id']
+                    };
+                    copies_to_move[item['call_number.id']] = new Array;
+                    copies_to_move[item['call_number.id']].push(item.id);
+                }
+            }
+        });
+
+        var promises = [];
+        angular.forEach(vols_to_move, function(vol) {
+            promises.push(egCore.net.request(
+                'open-ils.cat',
+                'open-ils.cat.call_number.find_or_create',
+                egCore.auth.token(),
+                vol.label,
+                vol.record,
+                xfer_target,
+                vol.prefix,
+                vol.suffix,
+                vol.label_class
+            ).then(function(resp) {
+                var evt = egCore.evt.parse(resp);
+                if (evt) return;
+                return egCore.net.request(
+                    'open-ils.cat',
+                    'open-ils.cat.transfer_copies_to_volume',
+                    egCore.auth.token(),
+                    resp.acn_id,
+                    copies_to_move[vol.id]
+                );
+            }));
+        });
+
+        angular.forEach(
+            items,
+            function(cp){
+                promises.push(
+                    function(){ service.add_barcode_to_list(cp.barcode) }
+                )
+            }
+        );
+        $q.all(promises);
+    }
+
+    service.transferItems = function (items){
+        var xfer_target = egCore.hatch.getLocalItem('eg.cat.item_transfer_target');
+        var copy_ids = service.gatherSelectedHoldingsIds(items);
+        if (xfer_target && copy_ids.length > 0) {
+            egCore.net.request(
+                'open-ils.cat',
+                'open-ils.cat.transfer_copies_to_volume',
+                egCore.auth.token(),
+                xfer_target,
+                copy_ids
+            ).then(
+                function(resp) { // oncomplete
+                    var evt = egCore.evt.parse(resp);
+                    if (evt) {
+                        egConfirmDialog.open(
+                            egCore.strings.OVERRIDE_TRANSFER_COPIES_TO_MARKED_VOLUME_TITLE,
+                            egCore.strings.OVERRIDE_TRANSFER_COPIES_TO_MARKED_VOLUME_BODY,
+                            {'evt_desc': evt}
+                        ).result.then(function() {
+                            egCore.net.request(
+                                'open-ils.cat',
+                                'open-ils.cat.transfer_copies_to_volume.override',
+                                egCore.auth.token(),
+                                xfer_target,
+                                copy_ids,
+                                { events: ['TITLE_LAST_COPY', 'COPY_DELETE_WARNING'] }
+                            );
+                        }).then(function() {
+                            angular.forEach(items, function(cp){service.add_barcode_to_list(cp.barcode)});
+                        });
+                    } else {
+                        angular.forEach(items, function(cp){service.add_barcode_to_list(cp.barcode)});
+                    }
+
+                },
+                null, // onerror
+                null // onprogress
+            );
+        }
+    }
+
+    service.mark_missing_pieces = function(copy) {
+        var b = copy.barcode();
+        var t = egCore.idl.toHash(copy.call_number()).record.title;
+        egConfirmDialog.open(
+            egCore.strings.CONFIRM_MARK_MISSING_TITLE,
+            egCore.strings.CONFIRM_MARK_MISSING_BODY,
+            { barcode : b, title : t }
+        ).result.then(function() {
+
+            // kick off mark missing
+            return egCore.net.request(
+                'open-ils.circ',
+                'open-ils.circ.mark_item_missing_pieces',
+                egCore.auth.token(), copy.id()
+            )
+
+        }).then(function(resp) {
+            var evt = egCore.evt.parse(resp); // should always produce event
+
+            if (evt.textcode == 'ACTION_CIRCULATION_NOT_FOUND') {
+                return egAlertDialog.open(
+                    egCore.strings.CIRC_NOT_FOUND, {barcode : copy.barcode()});
+            }
+
+            var payload = evt.payload;
+
+            // TODO: open copy editor inline?  new tab?
+
+            // print the missing pieces slip
+            var promise = $q.when();
+            if (payload.slip) {
+                // wait for completion, since it may spawn a confirm dialog
+                promise = egCore.print.print({
+                    context : 'default',
+                    content_type : 'text/html',
+                    content : payload.slip.template_output().data()
+                });
+            }
+
+            if (payload.letter) {
+                $scope.letter = payload.letter.template_output().data();
+            }
+
+            // apply patron penalty
+            if (payload.circ) {
+                promise.then(function() {
+                    egCirc.create_penalty(payload.circ.usr())
+                });
+            }
+
+        });
+    }
+
+    return service;
+}])

commit 85f6c2b8715a29d6c92c29132d2b05469de66e64
Author: Cesar Velez <Cesar.Velez at equinoxinitiative.org>
Date:   Fri Aug 11 16:00:29 2017 +0100

    LP#1685929-Adds more XUL parity to Webstaff checkIn
    
    Checkin Grid context actions like Add Items to Bucket, Show Holds on Bib, Show
    Last Few Circulations, Edit Item Attributes, Mark Item Missing Pieces,
    Print Spine Label, were missing from CheckIn in webstaff.
    
    Signed-off by: Cesar Velez <cesar.velez at equinoxinitiative.org>
    Signed-off-by: Galen Charlton <gmc at equinoxinitiative.org>
    
    Conflicts:
    	Open-ILS/web/js/ui/default/staff/cat/item/app.js

diff --git a/Open-ILS/src/templates/staff/circ/checkin/index.tt2 b/Open-ILS/src/templates/staff/circ/checkin/index.tt2
index dd2c0cf..4bc925d6 100644
--- a/Open-ILS/src/templates/staff/circ/checkin/index.tt2
+++ b/Open-ILS/src/templates/staff/circ/checkin/index.tt2
@@ -9,7 +9,18 @@
 <script src="[% ctx.media_prefix %]/js/ui/default/staff/services/ui.js"></script>
 <script src="[% ctx.media_prefix %]/js/ui/default/staff/services/user.js"></script>
 <script src="[% ctx.media_prefix %]/js/ui/default/staff/circ/services/circ.js"></script>
+<script>
+angular.module('egCoreMod').run(['egStrings', function(s) {
+s.CONFIRM_MARK_MISSING_TITLE = "[% l('Mark item as missing pieces?') %]";
+s.CONFIRM_MARK_MISSING_BODY =
+  "[% l('[_1] / [_2]', '{{barcode}}', '{{title}}') %]";
+s.CIRC_NOT_FOUND =
+  "[% l('No circulation found for item with barcode [_1].  Item not modified.', '{{barcode}}') %]"
+}])
+</script>
+
 [% INCLUDE 'staff/circ/share/circ_strings.tt2' %]
+<script src="[% ctx.media_prefix %]/js/ui/default/staff/cat/item/app.js"></script>
 <script src="[% ctx.media_prefix %]/js/ui/default/staff/circ/checkin/app.js"></script>
 <link rel="stylesheet" href="[% ctx.base_path %]/staff/css/circ.css" />
 [% END %]
diff --git a/Open-ILS/src/templates/staff/circ/checkin/t_checkin_table.tt2 b/Open-ILS/src/templates/staff/circ/checkin/t_checkin_table.tt2
index 0066cb3..5978e7b 100644
--- a/Open-ILS/src/templates/staff/circ/checkin/t_checkin_table.tt2
+++ b/Open-ILS/src/templates/staff/circ/checkin/t_checkin_table.tt2
@@ -8,7 +8,9 @@
   grid-controls="gridControls"
   persist-key="{{grid_persist_key}}"
   dateformat="{{$root.egDateAndTimeFormat}}">
-
+  
+  <eg-grid-action handler="add_copies_to_bucket" label="[% l('Add Items to Bucket') %]">
+  </eg-grid-action>
   <eg-grid-action 
     handler="fetchLastCircPatron"
     label="[% l('Retrieve Last Patron Who Circulated Item') %]">
@@ -17,15 +19,36 @@
     handler="showBackdateDialog"
     label="[% l('Backdate Post-Checkin') %]">
   </eg-grid-action>
-  <eg-grid-action 
+  <eg-grid-action
+    group="[% l('Show')"
     handler="showMarkDamaged"
     label="[% l('Mark Items Damaged') %]">
   </eg-grid-action>
+  <eg-grid-action
+    group="[% l('Show')"
+    handler="show_mark_missing_pieces"
+    label="[% l('Mark Missing Pieces') %]">
+  </eg-grid-action>
   <eg-grid-action 
     handler="abortTransit"
     label="[% l('Cancel Transits') %]">
   </eg-grid-action>
+  <!-- Show Group -->
+  <eg-grid-action handler="showBibHolds" group="[% l('Show') %]"
+    label="[% l('Record Holds') %]">
+  </eg-grid-action>
+  <eg-grid-action handler="showLastCircs" group="[% l('Show') %]"
+    label="[% l('Last Few Circs') %]">
+  </eg-grid-action>
+
+  <!-- Edit Group -->
+  <eg-grid-action handler="selectedHoldingsVolCopyEdit" group="[% l('Edit') %]"
+    label="[% l('Volumes and Items') %]">
+  </eg-grid-action>
 
+  <eg-grid-action handler="printSpineLabels" group="[% l('Print') %]"
+    label="[% l('Spine Labels') %]">
+  </eg-grid-action>
   <eg-grid-field label="[% l('Alert Msg') %]"   
     path="acp.alert_message"></eg-grid-field>
 
diff --git a/Open-ILS/web/js/ui/default/staff/cat/item/app.js b/Open-ILS/web/js/ui/default/staff/cat/item/app.js
index 65737ad..be24fd8 100644
--- a/Open-ILS/web/js/ui/default/staff/cat/item/app.js
+++ b/Open-ILS/web/js/ui/default/staff/cat/item/app.js
@@ -48,8 +48,8 @@ angular.module('egItemStatus',
 })
 
 .factory('itemSvc', 
-       ['egCore','egCirc','$uibModal','$q','$timeout','$window','egConfirmDialog',
-function(egCore , egCirc , $uibModal , $q , $timeout , $window , egConfirmDialog ) {
+       ['egCore','egCirc','$uibModal','$q','$timeout','$window','egConfirmDialog','egAlertDialog',
+function(egCore , egCirc , $uibModal , $q , $timeout , $window , egConfirmDialog, egAlertDialog ) {
 
     var service = {
         copies : [], // copy barcode search results
@@ -104,10 +104,12 @@ function(egCore , egCirc , $uibModal , $q , $timeout , $window , egConfirmDialog
         return egCore.pcrud.retrieve( 'acp', id, service.flesh)
             .then(function(copy) {return copy});
     }
+
     service.getCirc = function(id) {
         return egCore.pcrud.search('aacs', { target_copy : id },
             service.circFlesh).then(function(circ) {return circ});
     }
+
     service.getSummary = function(id) {
         return circ_summary = egCore.net.request(
             'open-ils.circ',
@@ -876,6 +878,59 @@ function(egCore , egCirc , $uibModal , $q , $timeout , $window , egConfirmDialog
         }
     }
 
+    service.mark_missing_pieces = function(copy) {
+        var b = copy.barcode();
+        var t = egCore.idl.toHash(copy.call_number()).record.title;
+        egConfirmDialog.open(
+            egCore.strings.CONFIRM_MARK_MISSING_TITLE,
+            egCore.strings.CONFIRM_MARK_MISSING_BODY,
+            { barcode : b, title : t }
+        ).result.then(function() {
+
+            // kick off mark missing
+            return egCore.net.request(
+                'open-ils.circ',
+                'open-ils.circ.mark_item_missing_pieces',
+                egCore.auth.token(), copy.id()
+            )
+
+        }).then(function(resp) {
+            var evt = egCore.evt.parse(resp); // should always produce event
+
+            if (evt.textcode == 'ACTION_CIRCULATION_NOT_FOUND') {
+                return egAlertDialog.open(
+                    egCore.strings.CIRC_NOT_FOUND, {barcode : copy.barcode()});
+            }
+
+            var payload = evt.payload;
+
+            // TODO: open copy editor inline?  new tab?
+
+            // print the missing pieces slip
+            var promise = $q.when();
+            if (payload.slip) {
+                // wait for completion, since it may spawn a confirm dialog
+                promise = egCore.print.print({
+                    context : 'default',
+                    content_type : 'text/html',
+                    content : payload.slip.template_output().data()
+                });
+            }
+
+            if (payload.letter) {
+                $scope.letter = payload.letter.template_output().data();
+            }
+
+            // apply patron penalty
+            if (payload.circ) {
+                promise.then(function() {
+                    egCirc.create_penalty(payload.circ.usr())
+                });
+            }
+
+        });
+    }
+
     return service;
 }])
 
diff --git a/Open-ILS/web/js/ui/default/staff/circ/checkin/app.js b/Open-ILS/web/js/ui/default/staff/circ/checkin/app.js
index 099b1be..8f2fb50 100644
--- a/Open-ILS/web/js/ui/default/staff/circ/checkin/app.js
+++ b/Open-ILS/web/js/ui/default/staff/circ/checkin/app.js
@@ -1,5 +1,5 @@
 angular.module('egCheckinApp', ['ngRoute', 'ui.bootstrap', 
-    'egCoreMod', 'egUiMod', 'egGridMod', 'egUserMod'])
+    'egCoreMod', 'egUiMod', 'egGridMod', 'egUserMod', 'egItemStatus'])
 
 .config(function($routeProvider, $locationProvider, $compileProvider) {
     $locationProvider.html5Mode(true);
@@ -34,8 +34,8 @@ angular.module('egCheckinApp', ['ngRoute', 'ui.bootstrap',
  * Manages checkin
  */
 .controller('CheckinCtrl',
-       ['$scope','$q','$window','$location','egCore','checkinSvc','egGridDataProvider','egCirc',
-function($scope , $q , $window , $location , egCore , checkinSvc , egGridDataProvider , egCirc)  {
+       ['$scope','$q','$window','$location', '$timeout','egCore','checkinSvc','egGridDataProvider','egCirc', 'itemSvc',
+function($scope , $q , $window , $location , $timeout , egCore , checkinSvc , egGridDataProvider , egCirc, itemSvc)  {
 
     $scope.focusMe = true;
     $scope.checkins = checkinSvc.checkins;
@@ -326,5 +326,53 @@ function($scope , $q , $window , $location , egCore , checkinSvc , egGridDataPro
         });
     }
 
+    $scope.add_copies_to_bucket = function(items){
+        var itemsIds = [];
+        angular.forEach(items, function(cp){
+            itemsIds.push(cp.acp.id());
+        });
+
+        itemSvc.add_copies_to_bucket(itemsIds);
+    }
+
+    $scope.showBibHolds = function(items){
+        var recordIds = [];
+        angular.forEach(items, function(i){
+            recordIds.push(i.acn.record());
+        });
+        angular.forEach(recordIds, function (r) {
+            var url = egCore.env.basePath + 'cat/catalog/record/' + r + '/holds';
+            $timeout(function() { $window.open(url, '_blank') });
+        });
+    }
+
+    $scope.showLastCircs = function(items){
+        var itemIds = [];
+        angular.forEach(items, function(cp){
+            itemIds.push(cp.acp.id());
+        });
+        angular.forEach(itemIds, function (id) {
+            var url = egCore.env.basePath + 'cat/item/' + id + '/circs';
+            $timeout(function() { $window.open(url, '_blank') });
+        });
+    }
+
+    $scope.selectedHoldingsVolCopyEdit = function (items) {
+        var itemObjs = [];
+        angular.forEach(items, function(i){
+            var h = egCore.idl.toHash(i);
+            h['call_number.record.id'] = h.record.doc_id;
+            itemObjs.push(h);
+        });
+        itemSvc.spawnHoldingsEdit(itemObjs,false,false);
+    }
+
+    $scope.show_mark_missing_pieces = function(items){
+        angular.forEach(items, function(i){
+            i.acp.call_number(i.acn);
+            i.acp.call_number().record(i.record);
+            itemSvc.mark_missing_pieces(i.acp);
+        });
+    }
 }])
 

-----------------------------------------------------------------------

Summary of changes:
 Open-ILS/src/templates/staff/cat/item/index.tt2    |    1 +
 .../templates/staff/cat/item/missing_pieces.tt2    |    1 +
 .../src/templates/staff/circ/checkin/index.tt2     |   11 +
 .../staff/circ/checkin/t_checkin_table.tt2         |   25 +-
 Open-ILS/web/js/ui/default/staff/cat/item/app.js   |  838 +------------------
 .../js/ui/default/staff/cat/item/missing_pieces.js |   56 +--
 .../web/js/ui/default/staff/circ/checkin/app.js    |   62 ++-
 .../web/js/ui/default/staff/circ/services/item.js  |  908 ++++++++++++++++++++
 8 files changed, 1010 insertions(+), 892 deletions(-)
 create mode 100644 Open-ILS/web/js/ui/default/staff/circ/services/item.js


hooks/post-receive
-- 
Evergreen ILS




More information about the open-ils-commits mailing list