From: Bill Erickson Date: Wed, 21 May 2014 16:37:38 +0000 (-0400) Subject: payments UI now accepts payments X-Git-Url: https://old-git.evergreen-ils.org/?a=commitdiff_plain;h=c5e7770963e02615148d0125a8c444a264ea5a06;p=working%2FEvergreen.git payments UI now accepts payments Signed-off-by: Bill Erickson --- diff --git a/Open-ILS/src/templates/staff/circ/patron/index.tt2 b/Open-ILS/src/templates/staff/circ/patron/index.tt2 index c17bce0a91..f4d2fabb3c 100644 --- a/Open-ILS/src/templates/staff/circ/patron/index.tt2 +++ b/Open-ILS/src/templates/staff/circ/patron/index.tt2 @@ -27,8 +27,7 @@ diff --git a/Open-ILS/src/templates/staff/circ/patron/t_bills.tt2 b/Open-ILS/src/templates/staff/circ/patron/t_bills.tt2 index 48a050adc1..0c57c789b5 100644 --- a/Open-ILS/src/templates/staff/circ/patron/t_bills.tt2 +++ b/Open-ILS/src/templates/staff/circ/patron/t_bills.tt2 @@ -23,19 +23,19 @@

[% l('Owed for Selected:') %]
-
{{owed_selected | currency}}
+
{{owed_selected() | currency}}
[% l('Pending Payment:') %]
{{pending_payment() | currency}}
[% l('Billed for Selected:') %]
-
{{billed_selected | currency}}
+
{{billed_selected() | currency}}
[% l('Pending Change:') %]
{{pending_change() | currency}}
[% l('Paid for Selected:') %]
-
{{paid_selected | currency}}
+
{{paid_selected() | currency}}
@@ -48,8 +48,13 @@
@@ -58,29 +63,25 @@ [% l('Payment Received') %]
- +
-
- -
+
+ +
-
-
-
+
-
-
-
[% INCLUDE 'staff/circ/patron/t_bills_list.tt2' %]
diff --git a/Open-ILS/src/templates/staff/circ/patron/t_bills_list.tt2 b/Open-ILS/src/templates/staff/circ/patron/t_bills_list.tt2 index 5392af1c86..2f59fded33 100644 --- a/Open-ILS/src/templates/staff/circ/patron/t_bills_list.tt2 +++ b/Open-ILS/src/templates/staff/circ/patron/t_bills_list.tt2 @@ -2,11 +2,24 @@ + + + + + + + + + diff --git a/Open-ILS/src/templates/staff/css/style.css.tt2 b/Open-ILS/src/templates/staff/css/style.css.tt2 index e0407827aa..e848693935 100644 --- a/Open-ILS/src/templates/staff/css/style.css.tt2 +++ b/Open-ILS/src/templates/staff/css/style.css.tt2 @@ -305,6 +305,10 @@ table.list tr.selected td { cursor: e-resize; } +.eg-grid-menu-item { + margin-right: 10px; +} + /* hack to make the header columns line up with the content columns when the scroll bar is visible along the right side of the content diff --git a/Open-ILS/src/templates/staff/share/t_autogrid.tt2 b/Open-ILS/src/templates/staff/share/t_autogrid.tt2 index 7960624176..237a5fcc48 100644 --- a/Open-ILS/src/templates/staff/share/t_autogrid.tt2 +++ b/Open-ILS/src/templates/staff/share/t_autogrid.tt2 @@ -6,11 +6,9 @@
-
-
{{mainLabel}}
-
+
{{mainLabel}}
-
+
@@ -22,12 +20,18 @@
- - + +
+ +
+ + +
diff --git a/Open-ILS/src/templates/staff/share/t_prompt_dialog.tt2 b/Open-ILS/src/templates/staff/share/t_prompt_dialog.tt2 new file mode 100644 index 0000000000..ce19832db1 --- /dev/null +++ b/Open-ILS/src/templates/staff/share/t_prompt_dialog.tt2 @@ -0,0 +1,21 @@ + +
+ + + +
diff --git a/Open-ILS/web/js/ui/default/staff/circ/patron/app.js b/Open-ILS/web/js/ui/default/staff/circ/patron/app.js index 5d265cc66d..75a4874f59 100644 --- a/Open-ILS/web/js/ui/default/staff/circ/patron/app.js +++ b/Open-ILS/web/js/ui/default/staff/circ/patron/app.js @@ -4,7 +4,7 @@ * Search, checkout, items out, holds, bills, edit, etc. */ -angular.module('egPatronApp', ['ngRoute', 'ui.bootstrap', 'ngLocale', +angular.module('egPatronApp', ['ngRoute', 'ui.bootstrap', 'egCoreMod', 'egUiMod', 'egGridMod', 'egUserMod']) .config(function($routeProvider, $locationProvider, $compileProvider) { diff --git a/Open-ILS/web/js/ui/default/staff/circ/patron/bills.js b/Open-ILS/web/js/ui/default/staff/circ/patron/bills.js index 4841ccf060..bc74f524ff 100644 --- a/Open-ILS/web/js/ui/default/staff/circ/patron/bills.js +++ b/Open-ILS/web/js/ui/default/staff/circ/patron/bills.js @@ -4,8 +4,8 @@ angular.module('egPatronApp') .factory('billSvc', - ['$q','egCore', -function($q , egCore) { + ['$q','egCore','patronSvc', +function($q , egCore , patronSvc) { var service = {}; @@ -16,6 +16,29 @@ function($q , egCore) { .then(function(summary) {return service.summary = summary}) } + service.applyPayment = function(type, payments, note) { + return egCore.net.request( + 'open-ils.circ', + 'open-ils.circ.money.payment', + egCore.auth.token(), { + userid : service.userId, + note : note || '', + payment_type : type, + payments : payments, + patron_credit : 0 + }, + patronSvc.current.last_xact_id() + ).then(function(resp) { + console.debug('payments: ' + js2JSON(resp)); + if (evt = egCore.evt.parse(resp)) + return alert(evt); + + // payment API returns the update xact id so we can track it + // for future payments without having to refresh the user. + patronSvc.current.last_xact_id(resp.last_xact_id); + }); + } + return service; }]) @@ -24,24 +47,27 @@ function($q , egCore) { * Manages Bills */ .controller('PatronBillsCtrl', - ['$scope','$q','$routeParams','$locale','egCore','egGridDataProvider','billSvc', -function($scope, $q , $routeParams, $locale , egCore , egGridDataProvider , billSvc) { + ['$scope','$q','$routeParams','$locale','egCore','egGridDataProvider','billSvc','egPromptDialog', +function($scope, $q , $routeParams, $locale , egCore , egGridDataProvider , billSvc , egPromptDialog) { $scope.initTab('bills', $routeParams.id); billSvc.userId = $routeParams.id; + // set up some defaults $scope.payment_amount = 0; - $scope.owed_selected = 0; - $scope.billed_selected = 0; - $scope.paid_selected = 0; - $scope.payment_type = 'cash'; + $scope.payment_type = 'cash_payment'; $scope.focus_payment = true; $scope.annotate_payment = false; + $scope.gridRevision = 0; + + billSvc.fetchSummary().then(function(s) {$scope.summary = s}); + // given a payment amount, determines how much of that is applied + // to selected transactions and how much is left over (change). function pending_payment_info() { - if ($scope.payment_amount >= $scope.owed_selected) { + if ($scope.payment_amount >= $scope.owed_selected()) { return { - payment : $scope.owed_selected, - change : $scope.payment_amount - $scope.owed_selected + payment : $scope.owed_selected(), + change : $scope.payment_amount - $scope.owed_selected() } } return { @@ -49,12 +75,33 @@ function($scope, $q , $routeParams, $locale , egCore , egGridDataProvider , bi change : 0 }; } + + // calculates amount owed, billed, and paid for selected items + function selected_payment_info() { + var info = {owed : 0, billed : 0, paid : 0}; + angular.forEach(selectedItems, function(item) { + info.owed += Number(item.balance_owed); + info.billed += Number(item.total_owed); + info.paid += Number(item.total_paid); + }); + return info; + } + $scope.pending_payment = function() { return pending_payment_info().payment; } $scope.pending_change = function() { return pending_payment_info().change; } + $scope.owed_selected = function() { + return selected_payment_info().owed; + } + $scope.billed_selected = function() { + return selected_payment_info().billed; + } + $scope.paid_selected = function() { + return selected_payment_info().paid; + } // Avoid using parens [e.g. (1.23)] to indicate negative numbers, // which is the Angular default. @@ -70,19 +117,75 @@ function($scope, $q , $routeParams, $locale , egCore , egGridDataProvider , bi item.payment_pending = 0; } - $scope.gridItemSelected = function(selected, deSelected) { - if (selected) { - $scope.owed_selected += Number(selected.balance_owed); - $scope.billed_selected += Number(selected.total_owed); - $scope.paid_selected += Number(selected.total_paid); - } else { - $scope.owed_selected -= Number(deSelected.balance_owed); - $scope.billed_selected -= Number(deSelected.total_owed); - $scope.paid_selected -= Number(deSelected.total_paid); + var selectedItems = []; + $scope.gridItemSelected = function(sel, desel, all) { + // keep a local cache of the selected items so we can + // calculate payment info from them. + selectedItems = all; + } + + function generatePayments() { + var payments = []; + var paymentAmount = $scope.pending_payment(); + + for (var i = 0; i < selectedItems.length; i++) { // for/break + var item = selectedItems[i]; + var owed = Number(item.balance_owed); + + if (paymentAmount > owed) { + // pending payment exceeds balance of current item. + // pay the entire item. + payments.push([item.id, owed]); + paymentAmount -= owed; + + } else { + // balance owed on the current item matches or exceeds + // the pending payment. Apply the full remainder of + // the payment to this item.. and we're done. + payments.push([item.id, paymentAmount]); + break; + } } + + return payments; } - billSvc.fetchSummary().then(function(s) {$scope.summary = s}); + function sendPayment(note) { + billSvc.applyPayment( + $scope.payment_type, generatePayments(), note) + .then(function() { + billSvc.fetchSummary().then(function(s) {$scope.summary = s}); + $scope.payment_amount = 0; + $scope.gridRevision++; // tell the grid to refresh itself + }) + } + + $scope.billPatron = function() { + // launch billing dialog + } + + $scope.showHistory = function() { + // go to bills/history + } + + $scope.selectRefunds = function() { + // select grid items where refunds are due + } + + $scope.printBills = function() { + // print selected bills using the bills print template + } + + $scope.applyPayment = function() { + if ($scope.annotate_payment) { + egPromptDialog.open( + egCore.strings.ANNOTATE_PAYMENT_MSG, + {ok : function(value) {sendPayment(value)}} + ); + } else { + sendPayment(); + } + } }]) diff --git a/Open-ILS/web/js/ui/default/staff/services/grid.js b/Open-ILS/web/js/ui/default/staff/services/grid.js index a555b686b1..ca2b44196b 100644 --- a/Open-ILS/web/js/ui/default/staff/services/grid.js +++ b/Open-ILS/web/js/ui/default/staff/services/grid.js @@ -73,7 +73,11 @@ angular.module('egGridMod', // function; if set, row index values will be hyperlinked and // the onclick for an item will call activateItem with the item // as the argument. - activateItem : '=' + activateItem : '=', + + // if set, we watch this scope variable for changes. If it + // changes, we refresh the grid. + revision : '=' }, // TODO: avoid hard-coded url @@ -165,6 +169,7 @@ angular.module('egGridMod', delete $scope.autoFields; } + if (!grid.dataProvider) { grid.selfManagedData = true; @@ -191,6 +196,13 @@ angular.module('egGridMod', delete $scope.query; } + if (angular.isDefined($scope.revision)) { + $scope.$watch('revision', function(newVal, oldVal) { + if (newVal != oldVal) + grid.dataProvider.load(true); + }); + } + grid.dataProvider.load = function(reset) { if (reset) grid.offset = 0; grid.collect(); @@ -369,16 +381,18 @@ angular.module('egGridMod', } // returns the unique identifier value for the provided item + // for internal consistency, indexValue is always coerced + // into a string. grid.indexValue = function(item) { if (angular.isObject(item)) { if (item !== null) { if (angular.isFunction(item[grid.indexField])) - return item[grid.indexField](); - return item[grid.indexField]; // flat data + return ''+item[grid.indexField](); + return ''+item[grid.indexField]; // flat data } } // passed a non-object; assume it's an index - return item; + return ''+item; } // fires the action handler function @@ -395,6 +409,14 @@ angular.module('egGridMod', ); } + grid.getItemByIndex = function(index) { + for (var i = 0; i < $scope.items.length; i++) { + var item = $scope.items[i]; + if (grid.indexValue(item) == index) + return item; + } + } + // selects one row after deselecting all of the others grid.selectOneItem = function(index) { $scope.selected = {}; @@ -467,7 +489,8 @@ angular.module('egGridMod', // handles click, control-click, and shift-click $scope.handleRowClick = function($event, item) { var index = grid.indexValue(item); - var wasSelected = Boolean($scope.selected[index]); + + var origSelected = Object.keys($scope.selected); if ($event.ctrlKey || $event.metaKey /* mac command */) { // control-click @@ -512,17 +535,26 @@ angular.module('egGridMod', if (grid.onItemSelected) { - var isSelected = Boolean($scope.selected[index]); + // multiple items may be selected / de-selected within a + // single click action. Find all that have changed state. + + var all = grid.getSelectedItems(); + + // look for items that were originally selected + // which are no longer selected + angular.forEach(origSelected, function(index) { + if (!$scope.selected[index]) { + grid.onItemSelected( + null, grid.getItemByIndex(index), all); + } + }); - if (isSelected != wasSelected) { - // something changed state; report it - var all = grid.getSelectedItems(); - if (isSelected) { + // look for items which are selected now, but were + // not originally selected + angular.forEach(all, function(item) { + if (origSelected.indexOf(grid.indexValue(item)) < 0) grid.onItemSelected(item, null, all); - } else { - grid.onItemSelected(null, item, all); - } - } + }); } } diff --git a/Open-ILS/web/js/ui/default/staff/services/ui.js b/Open-ILS/web/js/ui/default/staff/services/ui.js index 8605e26eea..ac090b107b 100644 --- a/Open-ILS/web/js/ui/default/staff/services/ui.js +++ b/Open-ILS/web/js/ui/default/staff/services/ui.js @@ -141,6 +141,43 @@ function($modal, $interpolate) { }]) /** + * egPromptDialog.open("some message goes {{here}}", { + * here : 'foo', + * ok : function(value) {console.log(value)}, + * cancel : function() {}}); + */ +.factory('egPromptDialog', + + ['$modal','$interpolate', +function($modal, $interpolate) { + var service = {}; + + service.open = function(message, msg_scope) { + return $modal.open({ + templateUrl: './share/t_prompt_dialog', + controller: ['$scope', '$modalInstance', + function($scope, $modalInstance) { + $scope.message = $interpolate(message)(msg_scope); + $scope.args = {value : ''}; + $scope.focus = true; + $scope.ok = function() { + if (msg_scope.ok) msg_scope.ok($scope.args.value); + $modalInstance.close() + } + $scope.cancel = function() { + if (msg_scope.cancel) msg_scope.cancel(); + $modalInstance.close() + } + } + ] + }) + } + + return service; +}]) + + +/** * Nested org unit selector modeled as a Bootstrap dropdown button. */ .directive('egOrgSelector', function() {