LP#1673857: add ability to set copy tags in volume/copy editor
authorGalen Charlton <gmc@equinoxinitiative.org>
Thu, 11 May 2017 15:29:25 +0000 (11:29 -0400)
committerGalen Charlton <gmc@equinoxinitiative.org>
Mon, 24 Jul 2017 15:29:10 +0000 (11:29 -0400)
The copy editor now has a 'Copy Tags' button that can be used
to assign or remove tags from a copy. A typeahead widget is
used to allow the user to select an existing tag, but users can
also use this interface to create an entirely new tag on the fly.

Signed-off-by: Galen Charlton <gmc@equinoxinitiative.org>
Signed-off-by: Josh Stompro <stomproj@larl.org>
Signed-off-by: Galen Charlton <gmc@equinoxinitiative.org>
Open-ILS/src/perlmods/lib/OpenILS/Application/Cat/AssetCommon.pm
Open-ILS/src/templates/staff/cat/volcopy/t_attr_edit.tt2
Open-ILS/src/templates/staff/cat/volcopy/t_copy_tags.tt2 [new file with mode: 0644]
Open-ILS/src/templates/staff/cat/volcopy/t_defaults.tt2
Open-ILS/web/js/ui/default/staff/cat/volcopy/app.js

index 4b81a46..14a52d4 100644 (file)
@@ -259,6 +259,51 @@ sub update_copy_notes {
 }
 
 
+sub update_copy_tags {
+    my($class, $editor, $copy) = @_;
+
+    return undef if $copy->isdeleted;
+
+    my $evt;
+    my $incoming_maps = $copy->tags;
+
+    for my $incoming_map (@$incoming_maps) {
+        next unless $incoming_map;
+
+        if ($incoming_map->isnew) {
+            next if ($incoming_map->isdeleted); # if it was added and deleted in the same session
+
+            my $tag_id;
+            if ($incoming_map->tag->isnew) {
+                my $new_tag = Fieldmapper::asset::copy_tag->new();
+                $new_tag->owner( $incoming_map->tag->owner );
+                $new_tag->label( $incoming_map->tag->label );
+                $new_tag->tag_type( $incoming_map->tag->tag_type );
+                $new_tag->pub( $incoming_map->tag->pub );
+                my $tag = $editor->create_asset_copy_tag($new_tag)
+                    or return $editor->event;
+                $tag_id = $tag->id;
+            } else {
+                $tag_id = $incoming_map->tag->id;
+            }
+            my $new_map = Fieldmapper::asset::copy_tag_copy_map->new();
+            $new_map->copy( $copy->id );
+            $new_map->tag( $tag_id );
+            $incoming_map = $editor->create_asset_copy_tag_copy_map($new_map)
+                or return $editor->event;
+
+        } elsif ($incoming_map->ischanged) {
+            $incoming_map = $editor->update_asset_copy_tag_copy_map($incoming_map)
+        } elsif ($incoming_map->isdeleted) {
+            $incoming_map = $editor->delete_asset_copy_tag_copy_map($incoming_map)
+        }
+    
+    }
+
+    return undef;
+}
+
+
 
 sub update_copy {
     my($class, $editor, $override, $vol, $copy, $retarget_holds, $force_delete_empty_bib) = @_;
@@ -370,6 +415,8 @@ sub update_fleshed_copies {
 
         my $notes = $copy->notes;
         $copy->clear_notes;
+        my $tags = $copy->tags;
+        $copy->clear_tags;
 
         if( $copy->isdeleted ) {
             $evt = $class->delete_copy($editor, $override, $vol, $copy, $retarget_holds, $force_delete_empty_bib);
@@ -394,6 +441,10 @@ sub update_fleshed_copies {
 
         $copy->notes( $notes );
         $evt = $class->update_copy_notes($editor, $copy);
+
+        $copy->tags( $tags );
+        $evt = $class->update_copy_tags($editor, $copy);
+
         return $evt if $evt;
     }
 
index 12f1f77..fc2c5e6 100644 (file)
                         ng-options="a.id() as a.name() for a in floating_list"
                     ></select>
                 </div>
+                <div class="col-md-6">
+                    <button
+                      class="btn btn-default"
+                      ng-disabled="!defaults.copy_tags"
+                      ng-click="copy_tags_dialog(workingGridControls.selectedItems())"
+                      type="button">
+                        [% l('Copy Tags') %]
+                    </button>
+                </div>
             </div>
         </div>
 
diff --git a/Open-ILS/src/templates/staff/cat/volcopy/t_copy_tags.tt2 b/Open-ILS/src/templates/staff/cat/volcopy/t_copy_tags.tt2
new file mode 100644 (file)
index 0000000..01932ad
--- /dev/null
@@ -0,0 +1,39 @@
+<form ng-submit="ok(note)" role="form">
+    <div class="modal-header">
+      <button type="button" class="close" ng-click="cancel()" 
+        aria-hidden="true">&times;</button>
+      <h4 class="modal-title">[% l('Manage Copy Tags') %]</h4>
+    </div>
+    <div class="modal-body">
+      <ul>
+        <li ng-repeat="map in tag_map" ng-show="!map.isdeleted()">
+            <span class="copy_tag_label">{{map.tag().label()}}</span>
+            <button type="button" ng-click="map.isdeleted(1)" class="btn btn-xs btn-warning">[% ('Remove') %]</button>
+        </li>
+      </ul>
+      <div class="row">
+        <div class="col-md-12 form-inline">
+          <div class="form-group">
+            <label for="tagType">[% l('Tag Type') %]</label>
+            <select class="form-control" name="tagType" ng-model="tag_type"
+                    ng-options="t.code() as t.label() for t in tag_types"></select>
+          </div>
+          <div class="form-group">
+            <label for="tagLabel">[% l('Tag') %]</label>
+            <input name="tabLabel" type="text" ng-model="selectedLabel" placeholder="[% l('Enter tag label...') %]"
+                uib-typeahead="tag for tag in getTags($viewValue)"
+                class="form-control"></input>
+          </div>
+          <button type="button" class="btn btn-sm btn-default" ng-click="addTag()">[% l('Add Tag') %]</button>
+        </div>
+      </div>
+    </div>
+    <div class="modal-footer">
+      <div class="row">
+        <div class="col-md-12 pull-right">
+          <input type="submit" class="btn btn-primary" value="[% l('OK') %]"/>
+          <button class="btn btn-warning" ng-click="cancel($event)">[% l('Cancel') %]</button>
+        </div>
+      </div>
+    </div>
+</form>
index 44b328b..507205d 100644 (file)
                         [% l('Floating') %]
                     </label>
                 </div>
+                <div class="col-xs-6">
+                    <label>
+                        <input type="checkbox" ng-change="saveDefaults()" ng-model="defaults.copy_tags"/>
+                        [% l('Add/Edit Copy Tags') %]
+                    </label>
+                </div>
             </div>
         </div>
 
index 644be2f..bbf44a2 100644 (file)
@@ -246,8 +246,9 @@ function(egCore , $q) {
     service.flesh = {   
         flesh : 3, 
         flesh_fields : {
-            acp : ['call_number','parts','stat_cat_entries', 'notes'],
-            acn : ['label_class','prefix','suffix']
+            acp : ['call_number','parts','stat_cat_entries', 'notes', 'tags'],
+            acn : ['label_class','prefix','suffix'],
+            acptcm : ['tag']
         }
     }
 
@@ -786,6 +787,7 @@ function($scope , $q , $window , $routeParams , $location , $timeout , egCore ,
         auto_gen_barcode : false,
         statcats : true,
         copy_notes : true,
+        copy_tags : true,
         attributes : {
             status : true,
             loan_duration : true,
@@ -1658,6 +1660,125 @@ function($scope , $q , $window , $routeParams , $location , $timeout , egCore ,
         });
     }
 
+    $scope.copy_tags_dialog = function(copy_list) {
+        if (!angular.isArray(copy_list)) copy_list = [copy_list];
+
+        return $uibModal.open({
+            templateUrl: './cat/volcopy/t_copy_tags',
+            animation: true,
+            controller:
+                   ['$scope','$uibModalInstance',
+            function($scope , $uibModalInstance) {
+
+                $scope.tag_map = [];
+                var tag_hash = {};
+                var shared_tags = {};
+                angular.forEach(copy_list, function (cp) {
+                    angular.forEach(cp.tags(), function(tag) {
+                        if (!(tag.tag().id() in shared_tags)) {
+                            shared_tags[tag.tag().id()] = 1;
+                        } else {
+                            shared_tags[tag.tag().id()]++;
+                        }
+                        if (!(tag.tag().id() in tag_hash)) {
+                            tag_hash[tag.tag().id()] = tag;
+                        }
+                    });
+                });
+                angular.forEach(tag_hash, function(value, key) {
+                    if (shared_tags[key] == copy_list.length) {
+                        $scope.tag_map.push(value);
+                    }
+                });
+
+                $scope.tag_types = [];
+                egCore.pcrud.retrieveAll('cctt', {order_by : { cctt : 'label' }}, {atomic : true}).then(function(list) {
+                    $scope.tag_types = list;
+                    $scope.tag_type = $scope.tag_types[0].code(); // just pick a default
+                });
+
+                $scope.getTags = function(val) {
+                    return egCore.pcrud.search('acpt',
+                        { 
+                            owner :  egCore.org.fullPath(egCore.auth.user().ws_ou(), true),
+                            label : { 'startwith' : {
+                                        transform: 'evergreen.lowercase',
+                                        value : [ 'evergreen.lowercase', val ]
+                                    }},
+                            tag_type : $scope.tag_type
+                        },
+                        { order_by : { 'acpt' : ['label'] } }, { atomic: true }
+                    ).then(function(list) {
+                        return list.map(function(item) {
+                            return item.label();
+                        });
+                    });
+                }
+
+                $scope.addTag = function() {
+                    var tagLabel = $scope.selectedLabel;
+                    // clear the typeahead
+                    $scope.selectedLabel = "";
+
+                    // first, check tags already associated with the copy
+                    var foundMatch = false;
+                    angular.forEach($scope.tag_map, function(tag) {
+                        if (tag.tag().label() ==  tagLabel && tag.tag().tag_type() == $scope.tag_type) {
+                            foundMatch = true;
+                            if (tag.isdeleted()) tag.isdeleted(0); // just deleting the mapping
+                        }
+                    });
+                    if (!foundMatch) {
+                        egCore.pcrud.search('acpt',
+                            { 
+                                owner : egCore.org.fullPath(egCore.auth.user().ws_ou(), true),
+                                label : tagLabel,
+                                tag_type : $scope.tag_type
+                            },
+                            { order_by : { 'acpt' : ['label'] } }, { atomic: true }
+                        ).then(function(list) {
+                            if (list.length > 0) {
+                                var newMap = new egCore.idl.acptcm();
+                                newMap.isnew(1);
+                                newMap.copy(copy_list[0].id());
+                                newMap.tag(egCore.idl.Clone(list[0]));
+                                $scope.tag_map.push(newMap);
+                            } else {
+                                var newTag = new egCore.idl.acpt();
+                                newTag.isnew(1);
+                                newTag.owner(egCore.auth.user().ws_ou());
+                                newTag.label(tagLabel);
+                                newTag.pub('t');
+                                newTag.tag_type($scope.tag_type);
+
+                                var newMap = new egCore.idl.acptcm();
+                                newMap.isnew(1);
+                                newMap.copy(copy_list[0].id());
+                                newMap.tag(newTag);
+                                $scope.tag_map.push(newMap);
+                            }
+                        });
+                    }
+                }
+
+                $scope.ok = function(note) {
+                    // in the multi-item case, this works OK for
+                    // adding new maps to existing tags, but doesn't handle
+                    // all possibilities
+                    angular.forEach(copy_list, function (cp) {
+                        cp.tags($scope.tag_map);
+                    });
+                    $uibModalInstance.close();
+                }
+
+                $scope.cancel = function($event) {
+                    $uibModalInstance.dismiss();
+                    $event.preventDefault();
+                }
+            }]
+        });
+    }
+
 }])
 
 .directive("egVolTemplate", function () {
@@ -1676,6 +1797,7 @@ function($scope , $q , $window , $routeParams , $location , $timeout , egCore ,
                     auto_gen_barcode : false,
                     statcats : true,
                     copy_notes : true,
+                    copy_tags : true,
                     attributes : {
                         status : true,
                         loan_duration : true,