webstaff: collect receiving data in one modal
authorMike Rylander <mrylander@gmail.com>
Mon, 22 May 2017 23:19:00 +0000 (19:19 -0400)
committerGalen Charlton <gmc@equinoxinitiative.org>
Tue, 30 May 2017 16:06:48 +0000 (12:06 -0400)
Signed-off-by: Mike Rylander <mrylander@gmail.com>
Open-ILS/src/perlmods/lib/OpenILS/Application/Serial.pm
Open-ILS/src/templates/staff/serials/t_batch_receive.tt2 [new file with mode: 0644]
Open-ILS/web/js/ui/default/staff/serials/directives/view-items-grid.js

index dbf2884..cbb04f2 100644 (file)
@@ -1221,6 +1221,11 @@ __PACKAGE__->register_method(
                  name => 'donor_unit_ids',
                  desc => 'hash of unit_ids => 1, keyed with ids of any units giving up items',
                  type => 'hash'
+            },
+            {
+                 name => 'extras',
+                 desc => 'hash of hashes, circ_mod code and copy_location id, keyed as above',
+                 type => 'hash'
             }
         ],
         'return' => {
@@ -1256,6 +1261,11 @@ __PACKAGE__->register_method(
                  name => 'donor_unit_ids',
                  desc => 'hash of unit_ids => 1, keyed with ids of any units giving up items',
                  type => 'hash'
+            },
+            {
+                 name => 'extras',
+                 desc => 'hash of hashes, circ_mod code and copy_location id, keyed as above',
+                 type => 'hash'
             }
         ],
         'return' => {
@@ -1288,7 +1298,7 @@ __PACKAGE__->register_method(
 );
 
 sub unitize_items {
-    my ($self, $conn, $auth, $items, $barcodes, $call_numbers, $donor_unit_ids) = @_;
+    my ($self, $conn, $auth, $items, $barcodes, $call_numbers, $donor_unit_ids, $extras) = @_;
 
     my $editor = new_editor("authtoken" => $auth, "xact" => 1);
     return $editor->die_event unless $editor->checkauth;
@@ -1366,7 +1376,11 @@ sub unitize_items {
                 $unit->{"note"} = "Item ID: " . $item->id;
                 return $unit;
             }
+
             $unit->barcode($barcodes->{$item->id}) if exists($barcodes->{$item->id});
+            $unit->location($extras->{copy_locations}->{$item->id}) if exists($extras->{copy_locations}->{$item->id});
+            $unit->circ_modifier($extras->{circ_mods}->{$item->id}) if exists($extras->{circ_mods}->{$item->id});
+
             my $evt =  _create_sunit($editor, $unit);
             return $evt if $evt;
             if ($unit_id == -2) {
diff --git a/Open-ILS/src/templates/staff/serials/t_batch_receive.tt2 b/Open-ILS/src/templates/staff/serials/t_batch_receive.tt2
new file mode 100644 (file)
index 0000000..3f78a0c
--- /dev/null
@@ -0,0 +1,131 @@
+<form ng-submit="ok(items)" role="form">
+<div class="modal-header">
+    <button type="button" class="close" ng-click="cancel()" 
+        aria-hidden="true">&times;</button>
+    <h4 class="modal-title">{{ title || "[% l('Construct new holding code') %]" }}</h4>
+</div>
+
+<div class="modal-body">
+  <div class="row">
+    <div class="col-md-2">
+      <b>[% l('Receive date') %]</b>
+    </div>
+    <div class="col-md-2">
+      <eg-date-input ng-model="receive_date"></eg-date-input>
+    </div>
+    <div class="col-md-2">
+      <label class="checkbox-inline">
+        <input type="checkbox" ng-model="barcode_items">[% l('Barcode Items') %]
+      </label>
+    </div>
+    <div class="col-md-2">
+      <label class="checkbox-inline">
+        <input type="checkbox" ng-disabled="!barcode_items" ng-model="auto_barcodes">[% l('Auto-Barcode') %]
+      </label>
+    </div>
+    <div class="col-md-2">
+      <label class="checkbox-inline" ng-show="items.length > 1">
+        <input type="checkbox" ng-disabled="force_bind" ng-model="bind">[% l('Bind') %]
+      </label>
+    </div>
+  </div>
+
+  <div class="row">
+    <div class="col-md-12"><hr/></div>
+  </div>
+
+  <div class="row">
+    <div class="col-md-2">
+      <b>[% l('Library : Distribution/Stream') %]</b>
+    </div>
+    <div class="col-md-2">
+      <b>[% l('Copy location') %]</b>
+    </div>
+    <div class="col-md-2">
+      <b>[% l('Call number') %]</b>
+    </div>
+    <div class="col-md-2">
+      <b>[% l('Circulation modifier') %]</b>
+    </div>
+  </div>
+
+  <div class="row">
+    <div class="col-md-2"></div>
+    <div class="col-md-2">
+      <select
+        class="form-control"
+        ng-model="selected_copy_location"
+        ng-options="l.id as l.name for l in acpl_list | orderBy:'name'">
+        <option value="">[% l('Template default') %]</option>
+      </select>
+    </div>
+    <div class="col-md-2">
+      <select
+        class="form-control"
+        ng-model="selected_call_number"
+        ng-options="l.label as l.label for l in acn_list | orderBy:'label_sortkey'">
+        <option value="">[% l('Default') %]</option>
+      </select>
+    </div>
+    <div class="col-md-2">
+      <select
+        class="form-control"
+        ng-model="selected_circ_mod"
+        ng-options="l.code as l.name for l in ccm_list | orderBy:'name'">
+        <option value="">[% l('Template default') %]</option>
+      </select>
+    </div>
+    <div class="col-md-2">
+      <div class="btn btn-primary" ng-click="apply_template_overrides()">[% l('Apply') %]</div>
+    </div>
+  </div>
+
+  <div class="row">
+    <div class="col-md-12"><hr/></div>
+  </div>
+
+  <div class="row" ng-repeat="item in items">
+    <div class="col-md-2">
+      {{item.stream().distribution().holding_lib().name()}}: {{item.stream().distribution().label()}}/{{item.stream().routing_label()}}
+    </div>
+    <div class="col-md-2">
+      <select
+        ng-disabled="bind_or_none($index)"
+        class="form-control"
+        ng-model="item._copy_location"
+        ng-options="l.id as l.name for l in acpl_list | orderBy:'name'">
+        <option value="">[% l('Template default') %]</option>
+      </select>
+    </div>
+    <div class="col-md-2">
+      <input ng-disabled="bind_or_none($index)" class="form-control"
+             ng-model="item._call_number" type="text"/>
+    </div>
+    <div class="col-md-2">
+      <select
+        ng-disabled="bind_or_none($index)"
+        class="form-control"
+        ng-model="item._circ_mod"
+        ng-options="l.code as l.name for l in ccm_list | orderBy:'name'">
+        <option value="">[% l('Template default') %]</option>
+      </select>
+    </div>
+    <div class="col-md-2">
+      <input ng-disabled="bind_or_none($index)" class="form-control" focus-me="$first"
+             ng-model="item._barcode" type="text" id="item_barcode_{{$index}}"
+             eg-enter="focus_next_barcode($index)"/>
+    </div>
+  </div>
+
+</div>
+
+<div class="modal-footer">
+  <div class="row">
+    <div class="col-md-8"></div>
+    <div class="col-md-4">
+      <input type="submit" class="btn btn-primary" value='{{ save_label || "[% l('Save') %]" }}'></input>
+      <button class="btn btn-warning" ng-click="cancel()">[% l('Cancel') %]</button>
+    </div>
+  </div>
+</div>
+</form>
index d883f86..19644f9 100644 (file)
@@ -241,30 +241,31 @@ function($scope , $q , egSerialsCoreSvc , egCore , egGridDataProvider ,
             }
         });
 
-        return process_next('receive', list);
+        return process_next('receive', list, $scope.receive_and_barcode);
     }
 
     $scope.receive_selected = function (list) {
         return process_next('receive', list.map(function(item) {
             return egCore.idl.Clone(egSerialsCoreSvc.itemMap[item.id]);
-        }));
+        }), $scope.receive_and_barcode);
     }
 
     $scope.bind_selected = function (list) {
         return process_next('bind', list.map(function(item) {
             return egCore.idl.Clone(egSerialsCoreSvc.itemMap[item.id]);
-        }), true);
+        }), true, true);
     }
 
-    function process_next (mode, list, receive_and_bind) {
+    function process_next (mode, list, do_barcode, bind) {
+        var bibId = $scope.bibId;
 
         if (!list.length) return $q.reject();
 
         var donor_unit_ids = {};
         angular.forEach(list, function (item) {
             if (item.unit()) donor_unit_ids[item.unit().id()] = 1;
-            if ($scope.receive_and_barcode) item.unit(-1);
-            if (receive_and_bind) item.unit(-2);
+            if (do_barcode) item.unit(-1);
+            if (bind) item.unit(-2);
         });
 
         var method; var success_label;
@@ -276,121 +277,140 @@ function($scope , $q , egSerialsCoreSvc , egCore , egGridDataProvider ,
             success_label = 'bound';
         } 
 
+        // deal with locations and circ mods for *NEW* units
+        var copy_locations = {};
+        var circ_mods = {};
+
         // deal with barcodes and call numbers for *NEW* units
         var barcodes = {};
         var call_numbers = {};
         var call_numbers_by_siss_and_sdist = {};
 
-        var r_and_b_barcode = '';
-        var r_and_b_callnumber = '';
-
         var deferred = $q.defer();
         var current_promise = deferred.promise;
         var last_promise;
-        if ($scope.receive_and_barcode || receive_and_bind) { // supplying a barcode (unit) to new items
-
-            angular.forEach(list, function (item) {
+        if (do_barcode || bind) { // supplying a barcode (unit) to new items
+
+            last_promise = current_promise.then(function(){ return $uibModal.open({
+                templateUrl: './serials/t_batch_receive',
+                size: 'lg',
+                windowClass: 'eg-wide-modal',
+                backdrop: 'static',
+                controller:
+                ['$scope', '$uibModalInstance', function($scope, $uibModalInstance) {
+
+                    $scope.receive_date = new Date();
+                    $scope.barcode_items = do_barcode;
+                    $scope.force_bind = bind;
+                    $scope.bind = bind;
+                    $scope.items = list;
+                    $scope.acn_list = [];
+                    $scope.acpl_list = [];
+
+                    var acpl_hash = {};
+                    var acn_hash = {};
+
+                    $scope.bind_or_none = function (index) {
+                        return !$scope.barcode_items || ($scope.bind && index > 0);
+                    }
 
-                if (last_promise) current_promise = last_promise;
+                    $scope.focus_next_barcode = function (index) {
+                        index++;
+                        $('#item_barcode_'+index).focus().select();
+                    }
 
-                // TODO: I18N
-                var prompt_text = 'for '+
-                    item.issuance().label()+
-                    ' from Distribution: '+item.stream().distribution().label()+
-                    '/'+item.stream().id()+':';
+                    $scope.$watch('auto_barcodes', function (n) {
+                        var bc = '@@AUTO';
+                        if (!n) bc = '';
 
-                if (receive_and_bind) {
-                    prompt_text = 'for the new unit:';
-                }
+                        angular.forEach($scope.items, function (i) {
+                            var _barcode = i._barcode;
+                            i._barcode = bc || i._old_barcode;
+                            i._old_barcode = _barcode;
+                        });
+                    });
 
-                last_promise = current_promise.then(function() {
-                    if (receive_and_bind && r_and_b_barcode) {
-                        barcodes[item.id()] = r_and_b_barcode;
-                        return $q.when();
+                    $scope.apply_template_overrides = function (e) {
+                        if ($scope.selected_call_number) {
+                            angular.forEach($scope.items, function (i) {
+                                i._call_number = $scope.selected_call_number;
+                            });
+                        }
+                        if ($scope.selected_circ_mod) {
+                            angular.forEach($scope.items, function (i) {
+                                i._circ_mod = $scope.selected_circ_mod;
+                            });
+                        }
+                        if ($scope.selected_copy_location) {
+                            angular.forEach($scope.items, function (i) {
+                                i._copy_location = $scope.selected_copy_location;
+                            });
+                        }
                     }
 
-                    return egPromptDialog.open(
-                        'Please enter a barcode ' + prompt_text, '@@AUTO',
-                        {ok : function(barcode) {
-                            if (barcode) {
-                                barcode = String( barcode ).replace(/\s/g,'');
-                                if (!barcode || barcode == 'null') {
-                                    barcode = '@@AUTO';
-/*
- *                                } else {
- *                                    // XXX test for barcode in use
- *                                    // disable alarm sound temporarily
- *                                    var sound_setting = obj.data.no_sound;
- *                                    if (!sound_setting) { // undefined or false
- *                                         obj.data.no_sound = true; obj.data.stash('no_sound');
- *                                    }
- *                                    var test = obj.network.simple_request('FM_ACP_RETRIEVE_VIA_BARCODE',[ barcode ]);
- *                                    if (typeof test.ilsevent == 'undefined') {
- *                                         alert('Another copy has barcode "' + barcode + '", defaulting to system-generated.');
- *                                         barcode = '@@AUTO';
- *                                    }
- *                                    if (!sound_setting) {
- *                                         obj.data.no_sound = sound_setting; obj.data.stash('no_sound');
- *                                    }
- */
-                                }
-                                barcodes[item.id()] = barcode;
-                                if (receive_and_bind && !r_and_b_barcode)
-                                    r_and_b_barcode = barcode;
-                            } else {
-                                barcodes[item.id()] = '@@AUTO';
-                            }
-                        }}
-                    ).result;
-                }).then(function() {
-                    if (receive_and_bind && r_and_b_callnumber) {
-                        call_numbers[item.id()] = r_and_b_callnumber;
-                        return $q.when();
-                    }
+                    var pile_o_promises = [$q.when()];
+                    // let's gather what we need...
+                    angular.forEach(list, function (i) {
+                        if (i.stream().distribution()[mode + '_call_number']())
+                            i._call_number = i.stream().distribution()[mode + '_call_number']().label();
 
-                    if (typeof call_numbers_by_siss_and_sdist[item.issuance().id() + '@' + item.stream().distribution().id()] == 'undefined') {
-                        var default_cn = 'DEFAULT';
-                        // if they defined a *_call_number, honor it as the default
-                        var preset_cn = item.stream().distribution()[mode + '_call_number']();
-                        if (preset_cn) {
-                            default_cn = preset_cn.label();
+                        if (i.stream().distribution()[mode + '_unit_template']()) {
+                            i._copy_location = i.stream().distribution()[mode + '_unit_template']().location();
+                            i._circ_mod = i.stream().distribution()[mode + '_unit_template']().circ_modifier();
                         }
-                    } else {
-                        // we have already seen this same issuance and distribution combo, so use the same call number
-                        call_numbers[item.id()] = call_numbers_by_siss_and_sdist[item.issuance().id() + '@' + item.stream().distribution().id()];
-                    }
+                    });
 
-                    return egPromptDialog.open('Please enter/adjust a call number'+prompt_text, default_cn,
-                        {ok : function(call_number) {
-                            if (call_number) {
-                                call_number = String( call_number ).replace(/\s/g,'');
-                                if (!call_number || call_number == 'null')
-                                    call_number = 'DEFAULT';
-                                call_numbers[item.id()] = call_number;
-                                call_numbers_by_siss_and_sdist[item.issuance().id() + '@' + item.stream().distribution().id()] = call_number;
-                                if (receive_and_bind && !r_and_b_callnumber)
-                                    r_and_b_callnumber = call_number;
-                            } else {
-                                call_numbers[item.id()] = 'DEFAULT';
-                            }
-                        }}
-                    ).result;
-                });
+                    pile_o_promises.push(egCore.pcrud.search(
+                        'acpl',
+                        {owning_lib : egCore.org.fullPath(egCore.auth.user().ws_ou(), true)},
+                        {},{ atomic : true }
+                    ).then(function (list) {
+                        $scope.acpl_list = list.map(function(i){return egCore.idl.toHash(i)});
+                        return $q.when();
+                    }));
+
+                    pile_o_promises.push(egCore.pcrud.search(
+                        'acn',
+                        {record : bibId, owning_lib : egCore.org.fullPath(egCore.auth.user().ws_ou(), true)},
+                        {},{ atomic : true }
+                    ).then(function (list) {
+                        $scope.acn_list = list.map(function(i){return egCore.idl.toHash(i)});
+                        return $q.when();
+                    }));
 
-            });
+                    pile_o_promises.push(egCore.pcrud.retrieveAll(
+                        'ccm', {}, { atomic : true }
+                    ).then(function (list) {
+                        $scope.ccm_list = list.map(function(i){return egCore.idl.toHash(i)});
+                        return $q.when();
+                    }));
+
+                    $q.all(pile_o_promises).then(function() {
+                        console.log('receive data collected');
+                    });
+
+                    $scope.ok = function(items) { $uibModalInstance.close(items) }
+                    $scope.cancel = function () { $uibModalInstance.dismiss() }
+                }]
+            }).result});
         }
 
         if (last_promise) current_promise = last_promise;
 
-        current_promise.then(function () {
-            console.log(list);
-            console.log(barcodes);
-            console.log(call_numbers);
-            console.log(donor_unit_ids);
+        current_promise.then(function (items) {
+
+            angular.forEach(items, function(i, index) {
+                copy_locations[i.id()] = i._copy_location;
+                circ_mods[i.id()] = i._circ_mod;
+                call_numbers[i.id()] = i._call_number || 'DEFAULT';
+                barcodes[i.id()] = i._barcode || '@@AUTO';
+                if (bind && index > 0) barcodes[i.id()] = items[0]._barcode;
+            });
 
             return egCore.net.request(
                 'open-ils.serial', method,
-                egCore.auth.token(), list, barcodes, call_numbers, donor_unit_ids
+                egCore.auth.token(), items, barcodes, call_numbers, donor_unit_ids,
+                    {circ_mods:circ_mods, copy_locations : copy_locations}
             ).then(
                 function(resp) {
                     var evt = egCore.evt.parse(resp);