webstaff: Vol/Copy edit (volumes ATM)
authorMike Rylander <mrylander@gmail.com>
Fri, 14 Aug 2015 21:13:30 +0000 (17:13 -0400)
committerJason Stephenson <jstephenson@mvlc.org>
Wed, 19 Aug 2015 17:39:20 +0000 (13:39 -0400)
Signed-off-by: Mike Rylander <mrylander@gmail.com>
Signed-off-by: Galen Charlton <gmc@esilibrary.com>
Signed-off-by: Jason Stephenson <jstephenson@mvlc.org>
Open-ILS/src/templates/staff/cat/volcopy/index.tt2 [new file with mode: 0644]
Open-ILS/src/templates/staff/cat/volcopy/t_edit.tt2 [new file with mode: 0644]
Open-ILS/src/templates/staff/cat/volcopy/t_view.tt2 [new file with mode: 0644]
Open-ILS/web/js/ui/default/staff/admin/workstation/app.js
Open-ILS/web/js/ui/default/staff/cat/volcopy/app.js [new file with mode: 0644]
Open-ILS/web/js/ui/default/staff/services/ui.js

diff --git a/Open-ILS/src/templates/staff/cat/volcopy/index.tt2 b/Open-ILS/src/templates/staff/cat/volcopy/index.tt2
new file mode 100644 (file)
index 0000000..d3f3ccf
--- /dev/null
@@ -0,0 +1,29 @@
+[%
+  WRAPPER "staff/base.tt2";
+  ctx.page_title = l("Volume/Copy Editor"); 
+  ctx.page_app = "egVolCopy";
+  ctx.page_ctrl = "EditCtrl";
+%]
+
+[% BLOCK APP_JS %]
+<script src="[% ctx.media_prefix %]/js/ui/default/staff/services/grid.js"></script>
+<script src="[% ctx.media_prefix %]/js/ui/default/staff/services/ui.js"></script>
+<script src="[% ctx.media_prefix %]/js/ui/default/staff/services/file.js"></script>
+<script src="[% ctx.media_prefix %]/js/ui/default/staff/services/eframe.js"></script>
+<script src="[% ctx.media_prefix %]/js/ui/default/staff/cat/volcopy/app.js"></script>
+<script src="[% ctx.media_prefix %]/js/ui/default/staff/cat/services/record.js"></script>
+[% END %]
+
+<style>
+  /* FIXME: MOVE ME */
+  #item-status-barcode {width: 16em;}
+  #item-status-form { 
+    margin-bottom: 20px; 
+  }
+</style>
+
+<div ng-view></div>
+
+[% END %]
+
+
diff --git a/Open-ILS/src/templates/staff/cat/volcopy/t_edit.tt2 b/Open-ILS/src/templates/staff/cat/volcopy/t_edit.tt2
new file mode 100644 (file)
index 0000000..4889e7c
--- /dev/null
@@ -0,0 +1,30 @@
+
+<style> input[type=number] { width: 50px } </style>
+<style> select { width: 80px } </style>
+<div class="container-fluid">
+    <div class="row">
+        <div class="col-xs-1">[% l('Library') %]</div>
+        <div class="col-xs-1">[% l('Volumes') %]</div>
+        <div class="col-xs-10">
+            <div class="container-fluid">
+                <div class="row">
+                    <div class="col-xs-1">[% l('Classification') %]</div>
+                    <div class="col-xs-1">[% l('Prefix') %]</div>
+                    <div class="col-xs-3">[% l('Call Number') %]</div>
+                    <div class="col-xs-1">[% l('Suffix') %]</div>
+                    <div class="col-xs-1">[% l('Copies') %]</div>
+                    <div class="col-xs-5">
+                        <div class="container-fluid">
+                            <div class="row">
+                                <div class="col-xs-6">[% l('Barcode') %]</div>
+                                <div class="col-xs-3">[% l('Copy #') %]</div>
+                                <div class="col-xs-3">[% l('Part') %]</div>
+                            </div>
+                        </div>
+                    </div>
+                </div>
+            </div>
+        </div>
+    </div> <!-- row -->
+    <eg-vol-edit ng-repeat="(lib,callnumbers) in data" record="{{record.id()}}" lib="{{lib}}" struct="data[lib]"></eg-vol-edit>
+</div> <!-- container -->
diff --git a/Open-ILS/src/templates/staff/cat/volcopy/t_view.tt2 b/Open-ILS/src/templates/staff/cat/volcopy/t_view.tt2
new file mode 100644 (file)
index 0000000..d31c26f
--- /dev/null
@@ -0,0 +1,35 @@
+<eg-record-summary record-id="record_id" record="summaryRecord"></eg-record-summary>
+
+<!-- tabbed copy data view -->
+<h1>[% l('Volume/Copy Editor') %]</h1>
+
+<div class="pad-vert"></div>
+
+<ul class="nav nav-tabs">
+  <li ng-class="{active : tab == 'edit'}">
+    <a ng-click="set_volcopy_tab('edit')" >[% l('Edit') %]</a>
+  </li>
+  <li ng-class="{active : tab == 'templates'}">
+    <a ng-click="set_volcopy_tab('templates')" >[% l('Templates') %]</a>
+  </li>
+  <li ng-class="{active : tab == 'defaults'}">
+    <a ng-click="set_volcopy_tab('defaults')" >[% l('defaults') %]</a>
+  </li>
+</ul>
+
+<div class="tab-content">
+  <div class="tab-pane active">
+    <div ng-show="tab == 'edit'">
+      <div ng-include="'[% ctx.base_path %]/staff/cat/volcopy/t_edit'"></div>
+    </div>
+<!--
+    <div ng-show="tab == 'templates'">
+      <div ng-include="'[% ctx.base_path %]/staff/cat/volcopy/t_'+tab"></div>
+    </div>
+    <div ng-show="tab == 'defaults'">
+      <div ng-include="'[% ctx.base_path %]/staff/cat/volcopy/t_'+tab"></div>
+    </div>
+-->
+  </div>
+</div>
+
index ae771ec..b313f05 100644 (file)
@@ -86,6 +86,7 @@ function($scope , $window , $location , egCore , egConfirmDialog) {
     }
 
     $scope.cant_have_users = function (id) { return !egCore.org.CanHaveUsers(id); };
+    $scope.cant_have_volumes = function (id) { return !egCore.org.CanHaveVolumes(id); };
 
     // redirect the user to the login page using the current
     // workstation as the workstation URL param
diff --git a/Open-ILS/web/js/ui/default/staff/cat/volcopy/app.js b/Open-ILS/web/js/ui/default/staff/cat/volcopy/app.js
new file mode 100644 (file)
index 0000000..a754e3e
--- /dev/null
@@ -0,0 +1,379 @@
+/**
+ * Vol/Copy Editor
+ */
+
+angular.module('egVolCopy',
+    ['ngRoute', 'ui.bootstrap', 'egCoreMod', 'egUiMod', 'egGridMod'])
+
+.filter('boolText', function(){
+    return function (v) {
+        return v == 't';
+    }
+})
+
+.config(function($routeProvider, $locationProvider, $compileProvider) {
+    $locationProvider.html5Mode(true);
+    $compileProvider.aHrefSanitizationWhitelist(/^\s*(https?|blob):/); // grid export
+
+    var resolver = {
+        delay : ['egStartup', function(egStartup) { return egStartup.go(); }]
+    };
+
+    $routeProvider.when('/cat/volcopy/:dataKey', {
+        templateUrl: './cat/volcopy/t_view',
+        controller: 'EditCtrl',
+        resolve : resolver
+    });
+
+})
+
+.factory('itemSvc', 
+       ['egCore','$q',
+function(egCore , $q) {
+
+    var service = {
+        tree : {}, // holds lib->cn->copy hash stack
+        copies : [] // raw copy list
+    };
+
+    // returns a promise resolved with the list of circ mods
+    service.get_classifications = function() {
+        if (egCore.env.acnc)
+            return $q.when(egCore.env.acnc.list);
+
+        return egCore.pcrud.retrieveAll('acnc', null, {atomic : true})
+        .then(function(list) {
+            egCore.env.absorbList(list, 'acnc');
+            return list;
+        });
+    };
+
+    service.get_prefixes = function(org) {
+        if (egCore.env.acnp)
+            return $q.when(egCore.env.acnp.list);
+
+        return egCore.pcrud.search('acnp',
+            {owning_lib : egCore.org.fullPath(org, true)},
+            null, {atomic : true}
+        ).then(function(list) {
+            egCore.env.absorbList(list, 'acnp');
+            return list;
+        });
+
+    };
+
+    service.get_suffixes = function(org) {
+        if (egCore.env.acns)
+            return $q.when(egCore.env.acns.list);
+
+        return egCore.pcrud.search('acns',
+            {owning_lib : egCore.org.fullPath(org, true)},
+            null, {atomic : true}
+        ).then(function(list) {
+            egCore.env.absorbList(list, 'acns');
+            return list;
+        });
+
+    };
+
+    service.bmp_parts = {};
+    service.get_parts = function(rec) {
+        if (service.bmp_parts[rec])
+            return $q.when(service.bmp_parts[rec]);
+
+        return egCore.pcrud.search('bmp',
+            {record : rec},
+            null, {atomic : true}
+        ).then(function(list) {
+            service.bmp_parts[rec] = list;
+            return list;
+        });
+
+    };
+
+    service.flesh = {   
+        flesh : 3, 
+        flesh_fields : {
+            acp : ['call_number','parts'],
+            acn : ['label_class','prefix','suffix']
+        },
+        select : { 
+            // avoid fleshing MARC on the bre
+            // note: don't add simple_record.. not sure why
+            bre : ['id','tcn_value','creator','editor'],
+        } 
+    }
+
+    service.addCopy = function (cp) {
+
+        var lib = cp.call_number().owning_lib();
+        var cn = cp.call_number().id();
+
+        // XXX need to change this data structure, likely...
+        if (!service.tree[lib]) service.tree[lib] = {};
+        if (!service.tree[lib][cn]) service.tree[lib][cn] = [];
+
+        service.tree[lib][cn].push(cp);
+        service.copies.push(cp);
+    }
+
+    service.fetchIds = function(idList) {
+        service.tree = {}; // clear the tree on fetch
+        service.copies = []; // clear the copy list on fetch
+        return egCore.pcrud.search('acp', { 'id' : idList }, service.flesh).then(null,null,
+            function(copy) {
+                service.addCopy(copy);
+            }
+        );
+    }
+
+    return service;
+}])
+
+.directive("egVolCopyEdit", function () {
+    return {
+        restrict: 'E',
+        replace: true,
+        template:
+            '<div class="row">'+
+                '<div class="col-xs-6"><input type="text" ng-model="barcode" ng-change="updateBarcode()"/></div>'+
+                '<div class="col-xs-2"><input type="number" ng-model="copy_number" ng-change="updateCopyNo()"/></div>'+
+                '<div class="col-xs-4"><eg-basic-combo-box list="parts" selected="part"></eg-basic-combo-box></div>'+
+            '</div>',
+
+        scope: { copy: "=", callNumber: "=" },
+        controller : ['$scope','itemSvc',
+            function ( $scope , itemSvc ) {
+                $scope.new_part_id = 0;
+
+                $scope.updateBarcode = function () { $scope.copy.barcode($scope.barcode) };
+                $scope.updateCopyNo = function () { $scope.copy.copy_number($scope.copy_number) };
+                $scope.updatePart = function () {
+                    var p = angular.filter($scope.part_list, function (x) {
+                        return x.label() == $scope.part
+                    });
+                    if (p.length > 0) { // preexisting part
+                        $scope.copy.parts(p)
+                    } else { // create one...
+                        var part = new egCore.idl.bmp();
+                        part.id( --$scope.new_part_id );
+                        part.isnew( true );
+                        part.label( $scope.part );
+                        part.record( $scope.callNumber.owning_lib() );
+                        $scope.copy.parts([part]);
+                    }
+                }
+
+                $scope.barcode = $scope.copy.barcode();
+                $scope.copy_number = $scope.copy.copy_number();
+
+                if ($scope.copy.parts()) {
+                    $scope.part = $scope.copy.parts()[0];
+                    if ($scope.part) $scope.part = $scope.part.label();
+                };
+
+                $scope.parts = [];
+                $scope.part_list = [];
+
+                itemSvc.get_parts($scope.callNumber.record()).then(function(list){
+                    $scope.part_list = list;
+                    angular.forEach(list, function(p){ $scope.parts.push(p.label()) });
+                });
+
+            }
+        ]
+
+    }
+})
+
+.directive("egVolRow", function () {
+    return {
+        restrict: 'E',
+        replace: true,
+        transclude: true,
+        template:
+            '<div class="row">'+
+                '<div class="col-xs-1">'+
+                    '<select ng-model="classification" ng-options="cl.name() for cl in classification_list track by idTracker(cl)"/>'+
+                '</div>'+
+                '<div class="col-xs-1">'+
+                    '<select ng-model="prefix" ng-change="updatePrefix()" ng-options="p.label() for p in prefix_list track by idTracker(p)"/>'+
+                '</div>'+
+                '<div class="col-xs-3"><input type="text" ng-change="updateLabel()" ng-model="label"/></div>'+
+                '<div class="col-xs-1">'+
+                    '<select ng-model="suffix" ng-change="updateSuffix()" ng-options="s.label() for s in suffix_list track by idTracker(s)"/>'+
+                '</div>'+
+                '<div class="col-xs-1"><input type="number" ng-model="copy_count" min="{{orig_copy_count}}" ng-change="changeCPCount()"></div>'+
+                '<div class="col-xs-5">'+
+                    '<div class="container-fluid">'+
+                        '<eg-vol-copy-edit ng-repeat="cp in copies track by idTracker(cp)" copy="cp" call-number="callNumber"></eg-vol-copy-edit>'+
+                    '</div>'+
+                '</div>'+
+            '</div>',
+
+        scope: { copies: "=" },
+        controller : ['$scope','itemSvc','egCore',
+            function ( $scope , itemSvc , egCore ) {
+                $scope.new_cp_id = 0;
+                $scope.callNumber =  $scope.copies[0].call_number();
+
+                $scope.idTracker = function (x) { if (x) return x.id() };
+
+                $scope.suffix_list = [];
+                itemSvc.get_suffixes($scope.callNumber.owning_lib()).then(function(list){
+                    $scope.suffix_list = list;
+                });
+                $scope.updateSuffix = function () { $scope.callNumber.suffix($scope.suffix) };
+
+                $scope.prefix_list = [];
+                itemSvc.get_prefixes($scope.callNumber.owning_lib()).then(function(list){
+                    $scope.prefix_list = list;
+                });
+                $scope.updatePrefix = function () { $scope.callNumber.prefix($scope.prefix) };
+
+                $scope.classification_list = [];
+                itemSvc.get_classifications().then(function(list){
+                    $scope.classification_list = list;
+                });
+                $scope.updateClassification = function () { $scope.callNumber.label_class($scope.classification) };
+
+                $scope.classification = $scope.callNumber.label_class();
+                $scope.prefix = $scope.callNumber.prefix();
+                $scope.suffix = $scope.callNumber.suffix();
+
+                $scope.label = $scope.callNumber.label();
+                $scope.updateLabel = function () { $scope.callNumber.label($scope.label) };
+
+                $scope.copy_count = $scope.copies.length;
+                $scope.orig_copy_count = $scope.copy_count;
+
+                $scope.changeCPCount = function () {
+                    while ($scope.copy_count > $scope.copies.length) {
+                        var cp = new egCore.idl.acp();
+                        cp.id( --$scope.new_cp_id );
+                        cp.isnew( true );
+                        cp.circ_lib( $scope.lib );
+                        cp.call_number( $scope.callNumber );
+                        $scope.copies.push( cp );
+                    }
+
+                    var how_many = $scope.copies.length - $scope.copy_count;
+                    if (how_many > 0) {
+                        $scope.copies.splice($scope.copy_count,how_many);
+                        $scope.callNumber.copies($scope.copies);
+                    }
+                }
+
+            }
+        ]
+
+    }
+})
+
+.directive("egVolEdit", function () {
+    return {
+        restrict: 'E',
+        replace: true,
+        template:
+            '<div class="row">'+
+                '<div class="col-xs-1"><eg-org-selector selected="owning_lib" disableTest="cant_have_vols"></eg-org-selector></div>'+
+                '<div class="col-xs-1"><input type="number" min="{{orig_cn_count}}" ng-model="cn_count" ng-change="changeCNCount()"/></div>'+
+                '<div class="col-xs-10">'+
+                    '<div class="container-fluid">'+
+                        '<eg-vol-row ng-repeat="(cn,copies) in struct track by cn" copies="copies"></eg-vol-row>'+
+                    '</div>'+
+                '</div>'+
+            '</div>',
+
+        scope: { struct: "=", lib: "@", record: "@" },
+        controller : ['$scope','itemSvc','egCore',
+            function ( $scope , itemSvc , egCore ) {
+                $scope.new_cn_id = 0;
+                $scope.first_cn = Object.keys($scope.struct)[0];
+                $scope.full_cn = $scope.struct[$scope.first_cn][0].call_number();
+
+                $scope.cn_count = Object.keys($scope.struct).length;
+                $scope.orig_cn_count = $scope.cn_count;
+
+                $scope.owning_lib = egCore.org.get($scope.lib);
+
+                $scope.cant_have_vols = function (id) { return !egCore.org.CanHaveVolumes(id); };
+
+                $scope.$watch('cn_count', function (n) {
+                    var o = Object.keys($scope.struct).length;
+                    if (n > o) { // adding
+                        for (var i = o; o < n; o++) {
+                            var cn = new egCore.idl.acn();
+                            cn.id( --$scope.new_cn_id );
+                            cn.isnew( true );
+                            cn.owning_lib( $scope.owning_lib );
+                            cn.record( $scope.full_cn.record() );
+
+                            var cp = new egCore.idl.acp();
+                            cp.id( --$scope.new_cp_id );
+                            cp.isnew( true );
+                            cp.circ_lib( $scope.owning_lib );
+                            cp.call_number( cn );
+
+                            $scope.struct[cn.id()] = [cp];
+                        }
+                    } else if (n < o) { // removing
+                        var how_many = o - n;
+                        var list = Object
+                                .keys($scope.struct)
+                                .sort(function(a, b){return a-b})
+                                .reverse();
+                        for (var i = how_many; i > 0; i--) {
+                            delete $scope.struct[list[i]];
+                        }
+                    }
+                });
+            }
+        ]
+
+    }
+})
+
+/**
+ * Edit controller!
+ */
+.controller('EditCtrl', 
+       ['$scope','$q','$routeParams','$location','$timeout','egCore','egNet','egGridDataProvider','itemSvc',
+function($scope , $q , $routeParams , $location , $timeout , egCore , egNet , egGridDataProvider , itemSvc) {
+
+    $timeout(function(){
+
+    var dataKey = $routeParams.dataKey;
+    console.debug('dataKey: ' + dataKey);
+
+    if (dataKey && dataKey.length > 0) {
+        $scope.tab = 'edit';
+        $scope.summaryRecord = null;
+        $scope.record_id = null;
+        $scope.data = {};
+
+        $scope.gridDataProvider = egGridDataProvider.instance({
+            get : function(offset, count) {
+                //return provider.arrayNotifier(itemSvc.copies, offset, count);
+                return this.arrayNotifier(itemSvc.copies, offset, count);
+            }
+        });
+
+        egNet.request(
+            'open-ils.actor',
+            'open-ils.actor.anon_cache.get_value',
+            dataKey, 'edit-these-copies'
+        ).then(function (data) {
+            $scope.record_id = data.record_id;
+            return itemSvc.fetchIds(data.copies);
+        }).then( function() {
+            $scope.data = itemSvc.tree;
+            $scope.gridDataProvider.refresh();
+        });
+    }
+
+    });
+
+}])
+
+
index 25312c6..5291d9a 100644 (file)
@@ -207,6 +207,36 @@ function($modal, $interpolate) {
     };
 })
 
+.directive('egBasicComboBox', function() {
+    return {
+        restrict: 'E',
+        replace: true,
+        scope: {
+            list: "=", // list of strings
+            selected: "="
+        },
+        template:
+            '<div class="input-group">'+
+                '<input type="text" class="form-control" ng-model="selected">'+
+                '<div class="input-group-btn">'+
+                    '<button type="button" class="btn btn-default dropdown-toggle" data-toggle="dropdown"><span class="caret"></span></button>'+
+                    '<ul class="dropdown-menu dropdown-menu-right" role="menu">'+
+                        '<li ng-repeat="item in list" class="input-lg"><a href="#" ng-click="changeValue(item)">{{item}}</a></li>'+
+                    '</ul>'+
+                '</div>'+
+            '</div>',
+        controller: ['$scope',
+            function($scope) {
+
+                $scope.changeValue = function (newVal) {
+                    $scope.selected = newVal;
+                }
+
+            }
+        ]
+    };
+})
+
 /**
  * Nested org unit selector modeled as a Bootstrap dropdown button.
  */