webstaff: UI for various serial notes collab/phasefx/webstaff-serials-notes
authorJason Etheridge <jason@equinoxinitiative.org>
Fri, 7 Jul 2017 19:37:54 +0000 (15:37 -0400)
committerJason Etheridge <jason@EquinoxInitiative.org>
Fri, 14 Jul 2017 20:00:23 +0000 (16:00 -0400)
These changes add menu options for Subscription Notes, Distribution Notes, and
Item Notes to the webstaff serials interface (under Manage Subscriptions and
Manage Issues).  They spawn dialogs similar to the existing Copy Notes dialog
from the Item Editor, and show and allow the editing of existing notes as well
as the creation of a new note.

I'm not attempting to implement any behavior involving the alert flag.  I don't
know whether that exists or not.

Signed-off-by: Jason Etheridge <jason@EquinoxInitiative.org>
Open-ILS/examples/fm_IDL.xml
Open-ILS/src/templates/staff/serials/t_notes.tt2 [new file with mode: 0644]
Open-ILS/src/templates/staff/serials/t_subscription_manager.tt2
Open-ILS/src/templates/staff/serials/t_view_items_grid.tt2
Open-ILS/web/js/ui/default/staff/serials/directives/subscription_manager.js
Open-ILS/web/js/ui/default/staff/serials/directives/view-items-grid.js

index 2309eb0..9faa6ab 100644 (file)
@@ -4976,7 +4976,7 @@ SELECT  usr,
                </permacrud>
        </class>
 
-       <class id="ssubn" controller="open-ils.cstore" oils_obj:fieldmapper="serial::subscription_note" oils_persist:tablename="serial.subscription_note" reporter:label="Subscription Note">
+       <class id="ssubn" controller="open-ils.cstore open-ils.pcrud" oils_obj:fieldmapper="serial::subscription_note" oils_persist:tablename="serial.subscription_note" reporter:label="Subscription Note">
                <fields oils_persist:primary="id" oils_persist:sequence="serial.subscription_note_id_seq">
                        <field reporter:label="ID" name="id" reporter:datatype="id"/>
                        <field reporter:label="Subscription" name="subscription" reporter:datatype="link"/>
@@ -4991,6 +4991,20 @@ SELECT  usr,
                        <link field="subscription" reltype="has_a" key="id" map="" class="ssub"/>
                        <link field="creator" reltype="has_a" key="id" map="" class="au"/>
                </links>
+               <permacrud xmlns="http://open-ils.org/spec/opensrf/IDL/permacrud/v1">
+                       <actions>
+                               <create permission="ADMIN_SERIAL_SUBSCRIPTION" context_field="owning_lib">
+                    <context link="subscription" field="owning_lib"/>
+                </create>
+                               <retrieve />
+                               <update permission="ADMIN_SERIAL_SUBSCRIPTION" context_field="owning_lib">
+                    <context link="subscription" field="owning_lib"/>
+                </update>
+                               <delete permission="ADMIN_SERIAL_SUBSCRIPTION" context_field="owning_lib">
+                    <context link="subscription" field="owning_lib"/>
+                </delete>
+                       </actions>
+               </permacrud>
        </class>
 
        <class id="sdist" controller="open-ils.cstore open-ils.pcrud" oils_obj:fieldmapper="serial::distribution" oils_persist:tablename="serial.distribution" reporter:label="Distribution">
@@ -5286,7 +5300,7 @@ SELECT  usr,
                </permacrud>
        </class>
 
-       <class id="sin" controller="open-ils.cstore" oils_obj:fieldmapper="serial::item_note" oils_persist:tablename="serial.item_note" reporter:label="Item Note">
+       <class id="sin" controller="open-ils.cstore open-ils.pcrud" oils_obj:fieldmapper="serial::item_note" oils_persist:tablename="serial.item_note" reporter:label="Item Note">
                <fields oils_persist:primary="id" oils_persist:sequence="serial.item_note_id_seq">
                        <field reporter:label="ID" name="id" reporter:datatype="id"/>
                        <field reporter:label="Item" name="item" reporter:datatype="link"/>
@@ -5301,7 +5315,22 @@ SELECT  usr,
                        <link field="item" reltype="has_a" key="id" map="" class="sitem"/>
                        <link field="creator" reltype="has_a" key="id" map="" class="au"/>
                </links>
-               <!-- Not available via PCRUD at this time -->
+               <permacrud xmlns="http://open-ils.org/spec/opensrf/IDL/permacrud/v1">
+                       <actions>
+                               <create permission="ADMIN_SERIAL_ITEM">
+                                       <context link="item" jump="stream.distribution" field="holding_lib" />
+                               </create>
+                               <retrieve permission="ADMIN_SERIAL_ITEM">
+                                       <context link="item" jump="stream.distribution" field="holding_lib" />
+                               </retrieve>
+                               <update permission="ADMIN_SERIAL_ITEM">
+                                       <context link="item" jump="stream.distribution" field="holding_lib" />
+                               </update>
+                               <delete permission="ADMIN_SERIAL_ITEM">
+                                       <context link="item" jump="stream.distribution" field="holding_lib" />
+                               </delete>
+                       </actions>
+               </permacrud>
        </class>
        <class id="sasum" controller="open-ils.cstore" oils_obj:fieldmapper="serial::any_summary" oils_persist:tablename="serial.any_summary" reporter:label="All Issues' Summaries" oils_persist:readonly="true">
                <fields>
diff --git a/Open-ILS/src/templates/staff/serials/t_notes.tt2 b/Open-ILS/src/templates/staff/serials/t_notes.tt2
new file mode 100644 (file)
index 0000000..06ed074
--- /dev/null
@@ -0,0 +1,103 @@
+<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 ng-if="note_type == 'subscription'" class="modal-title">[% l('New Subscription Note') %]</h4>
+      <h4 ng-if="note_type == 'distribution'" class="modal-title">[% l('New Distribution Note') %]</h4>
+      <h4 ng-if="note_type == 'item'"         class="modal-title">[% l('New Item Note') %]</h4>
+    </div>
+    <div class="modal-body">
+      <div class="row">
+        <div class="col-md-6">
+          <input class="form-control" type="text"
+            ng-model="note.title" placeholder="[% l('Title...') %]"/>
+        </div>
+        <div class="col-md-3">
+          <label>
+            <input type="checkbox" ng-model="note.pub"/>
+            [% l('Public Note') %]
+          </label>
+          <label>
+            <input type="checkbox" ng-model="note.alert"/>
+            [% l('Alert Note') %]
+          </label>
+        </div>
+      </div>
+      <div class="row pad-vert">
+        <div class="col-md-12">
+          <textarea class="form-control" 
+            ng-model="note.value" placeholder="[% l('Note...') %]">
+          </textarea>
+        </div>
+      </div>
+    </div>
+    <div class="modal-footer">
+      <div class="row">
+        <div class="col-md-2">
+          <input type="text" class="form-control" ng-hide="!require_initials" 
+            ng-model="initials" placeholder="[% l('Initials') %]" ng-required="require_initials"/>
+        </div>
+        <div class="col-md-10 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 class="row pad-vert" ng-if="note_list.length &gt; 0"> 
+        <div class="col-md-12">
+          <div class="row">
+            <div class="col-md-12">
+              <hr/>
+            </div>
+          </div>
+          <div class="row">
+            <div class="col-md-12">
+              <h4 ng-if="note_type == 'subscription'" class="pull-left">[% l('Existing Subscription Notes') %]</h4>
+              <h4 ng-if="note_type == 'distribution'" class="pull-left">[% l('Existing Distribution Notes') %]</h4>
+              <h4 ng-if="note_type == 'item'"         class="pull-left">[% l('Existing Item Notes') %]</h4>
+            </div>
+          </div>
+        </div>
+      </div>
+
+      <div class="row" ng-repeat="n in note_list" ng-init="pub = n.pub() == 't'; alert = n.alert() == 't'; title = n.title(); value = n.value(); deleted = n.isdeleted()">
+        <div class="col-md-12">
+          <div class="row">
+            <div class="col-md-6">
+              <input class="form-control" type="text" ng-change="n.title(title) && n.ischanged(1)"
+                ng-model="title" placeholder="[% l('Title...') %]" ng-disabled="deleted"/>
+            </div>
+            <div class="col-md-3">
+              <label>
+                <input type="checkbox" ng-model="pub" ng-change="n.pub(pub) && n.ischanged(1)" ng-disabled="deleted"/>
+                [% l('Public Note') %]
+              </label>
+              <label>
+                <input type="checkbox" ng-model="alert" ng-change="n.alert(alert) && n.ischanged(1)" ng-disabled="deleted"/>
+                [% l('Alert Note') %]
+              </label>
+            </div>
+            <div class="col-md-3">
+              <label>
+                <input type="checkbox" ng-model="deleted" ng-change="n.isdeleted(deleted)"/>
+                [% l('Deleted?') %]
+              </label>
+            </div>
+          </div>
+          <div class="row pad-vert">
+            <div class="col-md-12">
+              <textarea class="form-control" ng-change="n.value(value) && n.ischanged(1)"
+                ng-model="value" placeholder="[% l('Note...') %]" ng-disabled="deleted">
+              </textarea>
+            </div>
+          </div>
+          <div class="row">
+            <div class="col-md-12">
+              <hr/>
+            </div>
+          </div>
+        </div>
+      </div>
+
+    </div>
+</form>
index 803f443..c104e9f 100644 (file)
       label="[% l('Apply Binding Template') %]"></eg-grid-action>
     <eg-grid-action handler="additional_routing" disabled="need_one_selected"
       label="[% l('Additional Routing') %]"></eg-grid-action>
+    <eg-grid-action handler="subscription_notes" disabled="need_one_selected"
+      label="[% l('Subscription Notes') %]"></eg-grid-action>
+    <eg-grid-action handler="distribution_notes" disabled="need_one_selected"
+      label="[% l('Distribution Notes') %]"></eg-grid-action>
     <eg-grid-action handler="link_mfhd" disabled="need_one_selected"
       label="[% l('Link MFHD') %]"></eg-grid-action>
     <eg-grid-action handler="delete_subscription"
index 9ae5b98..189e8ce 100644 (file)
@@ -82,6 +82,9 @@
     <eg-grid-action handler="set_selected_as_not_held"
       label="[% l('Mark as not held') %]"></eg-grid-action>
 
+    <eg-grid-action handler="item_notes"
+      label="[% l('Item Notes') %]"></eg-grid-action>
+
     <eg-grid-action handler="reset_selected"
       label="[% l('Reset items') %]"></eg-grid-action>
 
index 9407b63..9404be6 100644 (file)
@@ -552,6 +552,73 @@ function($scope , $q , egSerialsCoreSvc , egCore , egGridDataProvider ,
             });
         });
     }
+    $scope.subscription_notes = function(rows) {
+        return $scope.notes('subscription',rows);
+    }
+    $scope.distribution_notes = function(rows) {
+        return $scope.notes('distribution',rows);
+    }
+    $scope.notes = function(note_type,rows) {
+        if (!rows) { return; }
+
+        function modal(existing_notes) {
+            $uibModal.open({
+                templateUrl: './serials/t_notes',
+                animation: true,
+                controller: 'NotesCtrl',
+                resolve : {
+                    note_type : function() { return note_type; },
+                    rows : function() {
+                        return rows;
+                    },
+                    notes : function() {
+                        return existing_notes;
+                    }
+                },
+                windowClass: 'app-modal-window',
+                backdrop: 'static',
+                keyboard: false
+            }).result.then(function(notes) {
+                console.log('results',notes);
+                egCore.pcrud.apply(notes).then(
+                    function(a) { console.log('toast here 1',a); },
+                    function(a) { console.log('toast here 2',a); }
+                );
+            });
+        }
+
+        if (rows.length == 1) {
+            var fm_hint;
+            var search_hash = {};
+            var search_opt = {};
+            switch(note_type) {
+                case 'subscription':
+                    fm_hint = 'ssubn';
+                    search_hash.subscription = rows[0]['id'];
+                    search_opt.order_by = { ssubn : 'create_date' };
+                break;
+                case 'distribution':
+                    fm_hint = 'sdistn';
+                    search_hash.distribution = rows[0]['sdist.id'];
+                    search_opt.order_by = { sdistn : 'create_date' };
+                break;
+                case 'item': default:
+                    fm_hint = 'sin';
+                    search_hash.item = rows[0]['si.id'];
+                    search_opt.order_by = { sin : 'create_date' };
+                break;
+            }
+            egCore.pcrud.search(fm_hint, search_hash, search_opt,
+                { atomic : true }
+            ).then(function(list) {
+                modal(list);
+            });
+        } else {
+                // support batch creation of notes across selections,
+                // but not editing
+                modal([]);
+        }
+    }
 
 }]
     }
@@ -799,3 +866,73 @@ function($scope , $uibModalInstance , egCore , rowInfo , routes ) {
         }
     });
 }])
+
+.controller('NotesCtrl',
+       ['$scope','$uibModalInstance','egCore','note_type','rows','notes',
+function($scope , $uibModalInstance , egCore , note_type , rows , notes ) {
+    $scope.note_type = note_type;
+    $scope.focusNote = true;
+    $scope.note = {
+        creator : egCore.auth.user().id(),
+        title   : '',
+        value   : '',
+        pub     : false,
+        'alert' : false,
+    };
+
+    $scope.require_initials = false;
+    egCore.org.settings([
+        'ui.staff.require_initials.copy_notes'
+    ]).then(function(set) {
+        $scope.require_initials = Boolean(set['ui.staff.require_initials.copy_notes']);
+    });
+
+    $scope.note_list = notes;
+
+    $scope.ok = function(note) {
+
+        var return_notes = [];
+        if (note.initials) note.value += ' [' + note.initials + ']';
+        if (   (typeof note.title != 'undefined' && note.title != '')
+            || (typeof note.value != 'undefined' && note.value != '')) {
+            angular.forEach(rows, function (r) {
+                console.log('r',r);
+                window.my_r = r;
+                var n;
+                switch(note_type) {
+                    case 'subscription':
+                        n = new egCore.idl.ssubn();
+                        n.subscription(r['id']);
+                        break;
+                    case 'distribution':
+                        n = new egCore.idl.sdistn();
+                        n.distribution(r['sdist.id']);
+                        break;
+                    case 'item':
+                    default:
+                        n = new egCore.idl.sin();
+                        n.item(r['si.id']);
+                }
+                n.isnew(true);
+                n.creator(note.creator);
+                n.pub(note.pub);
+                n['alert'](note['alert']);
+                n.title(note.title);
+                n.value(note.value);
+                return_notes.push( n );
+            });
+        }
+        angular.forEach(notes, function(n) {
+            if (n.ischanged() || n.isdeleted()) {
+                return_notes.push( n );
+            }
+        });
+        window.return_notes = return_notes;
+        $uibModalInstance.close(return_notes);
+    }
+
+    $scope.cancel = function($event) {
+        $uibModalInstance.dismiss();
+        $event.preventDefault();
+    }
+}])
index 9349ffe..627bb2c 100644 (file)
@@ -406,8 +406,144 @@ function($scope , $q , egSerialsCoreSvc , egCore , egGridDataProvider , orderByF
         return true;
     };
 
+    $scope.item_notes = function(rows) {
+        return $scope.notes('item',rows);
+    }
+    // TODO - refactor this, it's duplicated in subscription_manager.js
+    $scope.notes = function(note_type,rows) {
+        if (!rows) { return; }
+
+        function modal(existing_notes) {
+            $uibModal.open({
+                templateUrl: './serials/t_notes',
+                animation: true,
+                controller: 'NotesCtrl',
+                resolve : {
+                    note_type : function() { return note_type; },
+                    rows : function() {
+                        return rows;
+                    },
+                    notes : function() {
+                        return existing_notes;
+                    }
+                },
+                windowClass: 'app-modal-window',
+                backdrop: 'static',
+                keyboard: false
+            }).result.then(function(notes) {
+                console.log('results',notes);
+                egCore.pcrud.apply(notes).then(
+                    function(a) { console.log('toast here 1',a); },
+                    function(a) { console.log('toast here 2',a); }
+                );
+            });
+        }
+
+        if (rows.length == 1) {
+            var fm_hint;
+            var search_hash = {};
+            var search_opt = {};
+            switch(note_type) {
+                case 'subscription':
+                    fm_hint = 'ssubn';
+                    search_hash.subscription = rows[0]['id'];
+                    search_opt.order_by = { ssubn : 'create_date' };
+                break;
+                case 'distribution':
+                    fm_hint = 'sdistn';
+                    search_hash.distribution = rows[0]['sdist.id'];
+                    search_opt.order_by = { sdistn : 'create_date' };
+                break;
+                case 'item': default:
+                    fm_hint = 'sin';
+                    search_hash.item = rows[0]['id'];
+                    search_opt.order_by = { sin : 'create_date' };
+                break;
+            }
+            egCore.pcrud.search(fm_hint, search_hash, search_opt,
+                { atomic : true }
+            ).then(function(list) {
+                modal(list);
+            });
+        } else {
+                // support batch creation of notes across selections,
+                // but not editing
+                modal([]);
+        }
+    }
+
 }]
 
     }
 })
 
+// TODO - refactor this; it's duplicated in subscription_manager.js
+.controller('NotesCtrl',
+       ['$scope','$uibModalInstance','egCore','note_type','rows','notes',
+function($scope , $uibModalInstance , egCore , note_type , rows , notes ) {
+    $scope.note_type = note_type;
+    $scope.focusNote = true;
+    $scope.note = {
+        creator : egCore.auth.user().id(),
+        title   : '',
+        value   : '',
+        pub     : false,
+        'alert' : false,
+    };
+
+    $scope.require_initials = false;
+    egCore.org.settings([
+        'ui.staff.require_initials.copy_notes'
+    ]).then(function(set) {
+        $scope.require_initials = Boolean(set['ui.staff.require_initials.copy_notes']);
+    });
+
+    $scope.note_list = notes;
+
+    $scope.ok = function(note) {
+
+        var return_notes = [];
+        if (note.initials) note.value += ' [' + note.initials + ']';
+        if (   (typeof note.title != 'undefined' && note.title != '')
+            || (typeof note.value != 'undefined' && note.value != '')) {
+            angular.forEach(rows, function (r) {
+                console.log('r',r);
+                window.my_r = r;
+                var n;
+                switch(note_type) {
+                    case 'subscription':
+                        n = new egCore.idl.ssubn();
+                        n.subscription(r['id']);
+                        break;
+                    case 'distribution':
+                        n = new egCore.idl.sdistn();
+                        n.distribution(r['sdist.id']);
+                        break;
+                    case 'item':
+                    default:
+                        n = new egCore.idl.sin();
+                        n.item(r['id']);
+                }
+                n.isnew(true);
+                n.creator(note.creator);
+                n.pub(note.pub);
+                n['alert'](note['alert']);
+                n.title(note.title);
+                n.value(note.value);
+                return_notes.push( n );
+            });
+        }
+        angular.forEach(notes, function(n) {
+            if (n.ischanged() || n.isdeleted()) {
+                return_notes.push( n );
+            }
+        });
+        window.return_notes = return_notes;
+        $uibModalInstance.close(return_notes);
+    }
+
+    $scope.cancel = function($event) {
+        $uibModalInstance.dismiss();
+        $event.preventDefault();
+    }
+}])