6 * add-all actions only add visible/fetched items.
7 * remove all from bucket UI leaves busted pagination
8 * -- apply a refresh after item removal?
9 * problems with bucket view fetching by record ID instead of bucket item:
10 * -- dupe bibs always sort to the bottom
11 * -- dupe bibs result in more records displayed per page than requested
12 * -- item 'pos' ordering is not honored on initial load.
15 angular.module('egCatCopyBuckets',
16 ['ngRoute', 'ui.bootstrap', 'egCoreMod', 'egUiMod', 'egGridMod'])
18 .config(function($routeProvider, $locationProvider, $compileProvider) {
19 $locationProvider.html5Mode(true);
20 $compileProvider.aHrefSanitizationWhitelist(/^\s*(https?|blob):/); // grid export
22 var resolver = {delay : function(egStartup) {return egStartup.go()}};
24 $routeProvider.when('/cat/bucket/copy/pending/:id', {
25 templateUrl: './cat/bucket/copy/t_pending',
26 controller: 'PendingCtrl',
30 $routeProvider.when('/cat/bucket/copy/pending', {
31 templateUrl: './cat/bucket/copy/t_pending',
32 controller: 'PendingCtrl',
36 $routeProvider.when('/cat/bucket/copy/view/:id', {
37 templateUrl: './cat/bucket/copy/t_view',
38 controller: 'ViewCtrl',
42 $routeProvider.when('/cat/bucket/copy/view', {
43 templateUrl: './cat/bucket/copy/t_view',
44 controller: 'ViewCtrl',
48 // default page / bucket view
49 $routeProvider.otherwise({redirectTo : '/cat/bucket/copy/view'});
53 * bucketSvc allows us to communicate between the pending
54 * and view controllers. It also allows us to cache
55 * data for each so that data reloads are not needed on every
56 * tab click (i.e. route persistence).
58 .factory('bucketSvc', ['$q','egCore', function($q, egCore) {
61 allBuckets : [], // un-fleshed user buckets
62 barcodeString : '', // last scanned barcode
63 barcodeRecords : [], // last scanned barcode results
64 currentBucket : null, // currently viewed bucket
66 // per-page list collections
70 // fetches all staff/copy buckets for the authenticated user
71 // this function may only be called after startup.
72 fetchUserBuckets : function(force) {
73 if (this.allBuckets.length && !force) return;
75 return egCore.net.request(
77 'open-ils.actor.container.retrieve_by_class.authoritative',
78 egCore.auth.token(), egCore.auth.user().id(),
79 'copy', 'staff_client'
80 ).then(function(buckets) { self.allBuckets = buckets });
83 createBucket : function(name, desc) {
84 var deferred = $q.defer();
85 var bucket = new egCore.idl.ccb();
86 bucket.owner(egCore.auth.user().id());
88 bucket.description(desc || '');
89 bucket.btype('staff_client');
93 'open-ils.actor.container.create',
94 egCore.auth.token(), 'copy', bucket
95 ).then(function(resp) {
97 if (typeof resp == 'object') {
98 console.error('bucket create error: ' + js2JSON(resp));
101 deferred.resolve(resp);
106 return deferred.promise;
109 // edit the current bucket. since we edit the
110 // local object, there's no need to re-fetch.
111 editBucket : function(args) {
112 var bucket = service.currentBucket;
113 bucket.name(args.name);
114 bucket.description(args.desc);
115 bucket.pub(args.pub);
116 return egCore.net.request(
118 'open-ils.actor.container.update',
119 egCore.auth.token(), 'copy', bucket
124 // returns 1 if full refresh is needed
125 // returns 2 if list refresh only is needed
126 service.bucketRefreshLevel = function(id) {
127 if (!service.currentBucket) return 1;
128 if (service.bucketNeedsRefresh) {
129 service.bucketNeedsRefresh = false;
130 service.currentBucket = null;
133 if (service.currentBucket.id() != id) return 1;
137 // returns a promise, resolved with bucket, rejected if bucket is
139 service.fetchBucket = function(id) {
140 var refresh = service.bucketRefreshLevel(id);
141 if (refresh == 2) return $q.when(service.currentBucket);
143 var deferred = $q.defer();
147 'open-ils.actor.container.flesh.authoritative',
148 egCore.auth.token(), 'copy', id
149 ).then(function(bucket) {
150 var evt = egCore.evt.parse(bucket);
153 deferred.reject(evt);
156 service.currentBucket = bucket;
157 deferred.resolve(bucket);
160 return deferred.promise;
163 // deletes a single container item from a bucket by container item ID.
164 // promise is rejected on failure
165 service.detachCopy = function(itemId) {
166 var deferred = $q.defer();
169 'open-ils.actor.container.item.delete',
170 egCore.auth.token(), 'copy', itemId
171 ).then(function(resp) {
172 var evt = egCore.evt.parse(resp);
175 deferred.reject(evt);
178 console.log('detached bucket item ' + itemId);
179 deferred.resolve(resp);
182 return deferred.promise;
185 // delete bucket by ID.
186 // resolved w/ response on successful delete,
187 // rejected otherwise.
188 service.deleteBucket = function(id) {
189 var deferred = $q.defer();
192 'open-ils.actor.container.full_delete',
193 egCore.auth.token(), 'copy', id
194 ).then(function(resp) {
195 var evt = egCore.evt.parse(resp);
198 deferred.reject(evt);
201 deferred.resolve(resp);
203 return deferred.promise;
210 * Top-level controller.
211 * Hosts functions needed by all controllers.
213 .controller('CopyBucketCtrl',
214 ['$scope','$location','$q','$timeout','$modal',
215 '$window','egCore','bucketSvc',
216 function($scope, $location, $q, $timeout, $modal,
217 $window, egCore, bucketSvc) {
219 $scope.bucketSvc = bucketSvc;
220 $scope.bucket = function() { return bucketSvc.currentBucket }
222 // tabs: search, pending, view
223 $scope.setTab = function(tab) {
226 // for bucket selector; must be called after route resolve
227 bucketSvc.fetchUserBuckets();
230 $scope.loadBucketFromMenu = function(item, bucket) {
231 if (bucket) return $scope.loadBucket(bucket.id());
234 $scope.loadBucket = function(id) {
236 '/cat/bucket/copy/' +
237 $scope.tab + '/' + encodeURIComponent(id));
240 $scope.addToBucket = function(recs) {
241 if (recs.length == 0) return;
242 bucketSvc.bucketNeedsRefresh = true;
244 angular.forEach(recs,
246 var item = new egCore.idl.ccbi();
247 item.bucket(bucketSvc.currentBucket.id());
248 item.target_copy(rec.id);
251 'open-ils.actor.container.item.create',
252 egCore.auth.token(), 'copy', item
253 ).then(function(resp) {
255 // HACK: add the IDs of the added items so that the size
256 // of the view list will grow (and update any UI looking at
257 // the list size). The data stored is inconsistent, but since
258 // we are forcing a bucket refresh on the next rendering of
259 // the view pane, the list will be repaired.
260 bucketSvc.currentBucket.items().push(resp);
266 $scope.openCreateBucketDialog = function() {
268 templateUrl: './cat/bucket/copy/t_bucket_create',
270 ['$scope', '$modalInstance', function($scope, $modalInstance) {
271 $scope.focusMe = true;
272 $scope.ok = function(args) { $modalInstance.close(args) }
273 $scope.cancel = function () { $modalInstance.dismiss() }
275 }).result.then(function (args) {
276 if (!args || !args.name) return;
277 bucketSvc.createBucket(args.name, args.desc).then(
280 bucketSvc.viewList = [];
281 bucketSvc.allBuckets = []; // reset
282 bucketSvc.currentBucket = null;
284 '/cat/bucket/copy/' + $scope.tab + '/' + id);
290 $scope.openEditBucketDialog = function() {
292 templateUrl: './cat/bucket/copy/t_bucket_edit',
294 ['$scope', '$modalInstance', function($scope, $modalInstance) {
295 $scope.focusMe = true;
297 name : bucketSvc.currentBucket.name(),
298 desc : bucketSvc.currentBucket.description(),
299 pub : bucketSvc.currentBucket.pub() == 't'
301 $scope.ok = function(args) {
303 $scope.actionPending = true;
304 args.pub = args.pub ? 't' : 'f';
305 // close the dialog after edit has completed
306 bucketSvc.editBucket(args).then(
307 function() { $modalInstance.close() });
309 $scope.cancel = function () { $modalInstance.dismiss() }
315 // opens the delete confirmation and deletes the current
316 // bucket if the user confirms.
317 $scope.openDeleteBucketDialog = function() {
319 templateUrl: './cat/bucket/copy/t_bucket_delete',
321 ['$scope', '$modalInstance', function($scope, $modalInstance) {
322 $scope.bucket = function() { return bucketSvc.currentBucket }
323 $scope.ok = function() { $modalInstance.close() }
324 $scope.cancel = function() { $modalInstance.dismiss() }
326 }).result.then(function () {
327 bucketSvc.deleteBucket(bucketSvc.currentBucket.id())
329 bucketSvc.allBuckets = [];
330 $location.path('/cat/bucket/copy/view');
335 // retrieves the requested bucket by ID
336 $scope.openSharedBucketDialog = function() {
338 templateUrl: './cat/bucket/copy/t_load_shared',
340 ['$scope', '$modalInstance', function($scope, $modalInstance) {
341 $scope.focusMe = true;
342 $scope.ok = function(args) {
343 if (args && args.id) {
344 $modalInstance.close(args.id)
347 $scope.cancel = function() { $modalInstance.dismiss() }
349 }).result.then(function(id) {
350 // RecordBucketCtrl $scope is not inherited by the
351 // modal, so we need to call loadBucket from the
353 $scope.loadBucket(id);
359 .controller('PendingCtrl',
360 ['$scope','$routeParams','bucketSvc','egGridDataProvider', 'egCore',
361 function($scope, $routeParams, bucketSvc , egGridDataProvider, egCore) {
362 $scope.setTab('pending');
365 $scope.gridControls = {
366 setQuery : function(q) {
367 if (bucketSvc.pendingList.length)
368 return {id : bucketSvc.pendingList};
374 $scope.search = function() {
375 bucketSvc.barcodeRecords = [];
379 {barcode : bucketSvc.barcodeString, deleted : 'f'},
381 ).then(null, null, function(copy) {
382 bucketSvc.pendingList.push(copy.id());
383 $scope.gridControls.setQuery({id : bucketSvc.pendingList});
387 $scope.resetPendingList = function() {
388 bucketSvc.pendingList = [];
389 $scope.gridControls.setQuery({});
392 if ($routeParams.id &&
393 (!bucketSvc.currentBucket ||
394 bucketSvc.currentBucket.id() != $routeParams.id)) {
395 // user has accessed this page cold with a bucket ID.
396 // fetch the bucket for display, then set the totalCount
397 // (also for display), but avoid fully fetching the bucket,
398 // since it's premature, in this UI.
399 bucketSvc.fetchBucket($routeParams.id);
401 $scope.gridControls.setQuery();
404 .controller('ViewCtrl',
405 ['$scope','$q','$routeParams','bucketSvc', 'egCore',
407 function($scope, $q , $routeParams, bucketSvc, egCore,
410 $scope.setTab('view');
411 $scope.bucketId = $routeParams.id;
414 $scope.gridControls = {
415 setQuery : function(q) {
421 function drawBucket() {
422 return bucketSvc.fetchBucket($scope.bucketId).then(
424 var ids = bucket.items().map(
425 function(i){return i.target_copy()}
428 $scope.gridControls.setQuery({id : ids});
430 $scope.gridControls.setQuery({});
436 $scope.detachCopies = function(copies) {
438 angular.forEach(copies, function(rec) {
439 var item = bucketSvc.currentBucket.items().filter(
441 return (i.target_copy() == rec.id)
445 promises.push(bucketSvc.detachCopy(item[0].id()));
448 bucketSvc.bucketNeedsRefresh = true;
449 return $q.all(promises).then(drawBucket);
452 $scope.deleteCopiesFromCatalog = function(copies) {
453 egConfirmDialog.open(
454 egCore.strings.CONFIRM_DELETE_COPY_BUCKET_ITEMS_FROM_CATALOG,
456 ).result.then(function() {
457 var fleshed_copies = [];
459 angular.forEach(copies, function(i) {
463 'open-ils.search.asset.copy.fleshed2.retrieve',
465 ).then(function(copy) {
468 fleshed_copies.push(copy);
472 $q.all(promises).then(function() {
475 'open-ils.cat.asset.copy.fleshed.batch.update',
476 egCore.auth.token(), fleshed_copies, true
477 ).then(function(resp) {
478 var evt = egCore.evt.parse(resp);
480 egConfirmDialog.open(
481 egCore.strings.OVERRIDE_DELETE_COPY_BUCKET_ITEMS_FROM_CATALOG_TITLE,
482 egCore.strings.OVERRIDE_DELETE_COPY_BUCKET_ITEMS_FROM_CATALOG_BODY,
483 {'evt_desc': evt.desc}
484 ).result.then(function() {
487 'open-ils.cat.asset.copy.fleshed.batch.update.override',
488 egCore.auth.token(), fleshed_copies, true,
489 { events: ['TITLE_LAST_COPY', 'COPY_DELETE_WARNING'] }
490 ).then(function(resp) {
491 bucketSvc.bucketNeedsRefresh = true;
496 bucketSvc.bucketNeedsRefresh = true;
503 // fetch the bucket; on error show the not-allowed message
505 drawBucket()['catch'](function() { $scope.forbidden = true });