<script src="[% ctx.media_prefix %]/js/ui/default/staff/circ/patron/checkout.js"></script>
<script src="[% ctx.media_prefix %]/js/ui/default/staff/circ/patron/items_out.js"></script>
<script src="[% ctx.media_prefix %]/js/ui/default/staff/circ/patron/holds.js"></script>
+<script src="[% ctx.media_prefix %]/js/ui/default/staff/circ/patron/bills.js"></script>
<!-- TODO: APP_JS should really be called APP_ADDONS or some such.
It just means "load these things, too, and load them last" -->
-<div class="row pad-vert">
- <div class="col-md-10 col-md-offset-1">
- <div class="alert alert-warning">[% l('BILLS Development Pending') %]</div>
+
+<div class="row">
+ <div class="col-md-7">
+
+ <div class="row">
+ <div class="col-md-6">
+ <div class="strong-text-4">
+ [% l('Total Owed: [_1]', '{{summary.balance_owed() | currency}}') %]
+ </div>
+ </div>
+ <div class="col-md-6">
+ <!-- TODO -->
+ <div class="strong-text-2">
+ [% l('Refunds Available: [_1]', '{{0 | currency}}') %]
+ </div>
+ </div>
+ </div>
+ <div class="row">
+ <div class="col-md-6">
+ <div class="strong-text-4">
+ [% l('Total Selected: [_1]', '{{total_selected | currency}}') %]
+ </div>
+ </div>
+ <div class="col-md-6">
+ <!-- FIXME: check patron credit display settings -->
+ <div class="strong-text-2">
+ [% l('Credit Available: [_1]', '{{patron().credit_forward_balance() | currency}}') %]
+ </div>
+ </div>
+ </div>
+ </div><!-- col -->
+
+ <div class="col-md-5">
+ <form role="form" class="form-horizontal" ng-submit="applyPayment()">
+ <fieldset>
+ <legend>[% l('Pay Bill') %]</legend>
+
+ <div class="form-group">
+ <label for="type-input" class="col-md-6 control-label">[% l('Payment Type') %]</label>
+ <div class="col-md-6">
+ <select ng-model="payment_type" class="form-control">
+ <option value="cash" selected="selected">[% l('Cash') %]</option>
+ <option value="check" selected="selected">[% l('Check') %]</option>
+ </select>
+ </div>
+ </div>
+ <div class="form-group">
+ <label for="amount-input" class="col-md-6 control-label">
+ [% l('Payment Received') %]
+ </label>
+ <div class="col-md-6">
+ <input type="number" min="0" id="amount-input"
+ focus-me="focus_payment" value="0" class="form-control col-md-6 "/>
+ </div>
+ </div>
+ <div class="form-group pull-right">
+ <div class="col-md-12">
+ <input id="annotate-payment" type="checkbox" ng-model="annotate_payment"/>
+ <label for="annotate-payment" class="ccontrol-label">[% l('Annotate') %]</label>
+ <button ng-click="applyDistribution($event)">[% l('See Distribution') %]</button>
+ <button type="submit">[% l('Apply Payment') %]</button>
+ </div>
+ </div>
+
+ </fieldset>
+ </form>
</div>
+
+</div>
+
+
+<div class="pad-vert">
+[% INCLUDE 'staff/circ/patron/t_bills_list.tt2' %]
</div>
--- /dev/null
+
+<eg-grid
+ idl-class="mobts"
+ query="gridQuery"
+ main-label="[% l('Patron Bills') %]"
+ on-item-retrieved="gridItemRetrieved"
+ on-item-selected="gridItemSelected"
+ persist-key="circ.patron.bills">
+ <eg-grid-field label="[% ('Balance Owed') %]" path='balance_owed'></eg-grid-field>
+ <eg-grid-field label="[% ('Bill #') %]" path='id'></eg-grid-field>
+ <eg-grid-field label="[% ('Start') %]" path='xact_start'></eg-grid-field>
+ <eg-grid-field label="[% ('Total Billed') %]" path='total_owed'></eg-grid-field>
+ <eg-grid-field label="[% ('Total Paid') %]" path='total_paid'></eg-grid-field>
+ <eg-grid-field label="[% ('Type') %]" path='xact_type'></eg-grid-field>
+ <eg-grid-field label="[% ('Title') %]" name='title'
+ path='circulation.target_copy.call_number.record.simple_record.title'>
+ </eg-grid-field>
+ <!-- virtual field -->
+ <eg-grid-field label="[% ('Payment Pending') %]" name="payment_pending"></eg-grid-field>
+</eg-grid>
+
main-label="[% l('Patron Search Results') %]"
items-provider="patronSearchGridProvider"
activate-item="activatePatron"
+ on-item-selected="gridItemSelected"
persist-key="circ.patron.search">
<eg-grid-field label="[% ('ID') %]" path='id' visible></eg-grid-field>
<eg-grid-field label="[% ('Card') %]" path='card.barcode' visible></eg-grid-field>
margin-left: 10px;
}
+
+.strong-text {
+ font-weight: bold;
+}
+.strong-text-1 {
+ font-size: 110%;
+ font-weight: bold;
+}
+.strong-text-2 {
+ font-size: 120%;
+ font-weight: bold;
+}
+.strong-text-3 {
+ font-size: 130%;
+ font-weight: bold;
+}
+.strong-text-4 {
+ font-size: 140%;
+ font-weight: bold;
+}
+
+.currency-input {
+ width: 8em;
+}
+
/* ----------------------------------------------------------------------
* Grid
* ---------------------------------------------------------------------- */
<!-- otherwise, simply display the item value, which may
pass through datatype-specific filtering. -->
<span ng-if="!col.template">
- {{itemFieldValue(item, col)}}
+ {{itemFieldValue(item, col) | egGridValueFilter:col}}
</span>
</div>
</div>
return {offset : 0};
}
- // show the user summary for the first selected user
- provider.select = function(items) {
- if (items[0]) {
- var user = items[0];
- patronSvc.setDefault(null, user);
- }
+ // FIXME: use onItemSelect instead
+ $scope.gridItemSelected = function(item) {
+ if (item) // null if de-selecting
+ patronSvc.setDefault(null, item);
}
provider.get = function(offset, count) {
}])
/**
- * Manages edit
- */
-.controller('PatronBillsCtrl',
- ['$scope','$routeParams','egCore',
-function($scope, $routeParams, egCore) {
- $scope.initTab('bills', $routeParams.id);
-}])
-
-
-/**
* Manages messages
*/
.controller('PatronMessagesCtrl',
--- /dev/null
+
+/* Billing Service */
+
+angular.module('egPatronApp')
+
+.factory('billSvc',
+ ['$q','egCore',
+function($q , egCore) {
+
+ var service = {};
+
+ // user billing summary
+ service.fetchSummary = function() {
+ return egCore.pcrud.retrieve(
+ 'mous', service.userId, {}, {authoritative : true})
+ .then(function(summary) {return service.summary = summary})
+ }
+
+ return service;
+}])
+
+
+/**
+ * Manages Bills
+ */
+.controller('PatronBillsCtrl',
+ ['$scope','$q','$routeParams','egCore','egGridDataProvider','billSvc',
+function($scope, $q , $routeParams, egCore , egGridDataProvider , billSvc) {
+ $scope.initTab('bills', $routeParams.id);
+ billSvc.userId = $routeParams.id;
+
+ $scope.total_selected = 0;
+ $scope.payment_type = 'cash';
+ $scope.focus_payment = true;
+ $scope.annotate_payment = false;
+
+
+ var query = {usr : billSvc.userId, balance_owed : {'<>' : 0}};
+ $scope.gridQuery = function() {return query};
+ $scope.gridItemRetrieved = function(item) {
+ item.payment_pending = 0;
+ }
+
+ $scope.gridItemSelected = function(selected, deSelected) {
+ if (selected) {
+ $scope.total_selected += Number(selected.balance_owed);
+ } else {
+ $scope.total_selected += Number(deSelected.balance_owed);
+ }
+ }
+
+ billSvc.fetchSummary().then(function(s) {$scope.summary = s});
+
+}])
+
+
+
// are absorbed by the grid.
onItemRetrieved : '=',
+ // function called with (selectedItem, deSelectedItem, allSelectedItems)
+ onItemSelected : '=',
+
// function; if set, row index values will be hyperlinked and
// the onclick for an item will call activateItem with the item
// as the argument.
// items needed only by the grid; remove from scope
angular.forEach(
- ['idlClass', 'onItemRetrieved','persistKey'],
+ ['idlClass','onItemRetrieved','onItemSelected','persistKey'],
function(field) {
grid[field] = $scope[field];
delete $scope[field];
grid.columnsProvider.hideAllColumns();
}
- if ($scope.autoFields) {
+ if (!grid.indexField && grid.idlClass)
grid.indexField = egCore.idl.classes[grid.idlClass].pkey;
+
+ if ($scope.autoFields) {
if (grid.autoLabel)
$scope.mainLabel = egCore.idl.classes[grid.idlClass].label;
grid.columnsProvider.compileAutoColumns();
query : $scope.query
});
+ if (!grid.dataProvider.query) {
+ console.error(
+ "Grid query is required when no data provider is supplied");
+ return;
+ }
+
$scope.$watch(
function() { return grid.dataProvider.query() },
function(newVal, oldVal) {
// link columns to scope after loadConfig(), since it
// replaces the columns array.
$scope.columns = grid.columnsProvider.columns;
+ //if (grid.selfManagedData) grid.collect();
});
}
}
}
- grid.informSelection = function() {
- var items = [];
- angular.forEach(Object.keys($scope.selected),
- function(index) {
- items.push($scope.items[grid.indexOf(index)]);
- }
- );
- grid.dataProvider.select(items);
- }
-
-
// returns true if item1 appears in the list before item2;
// false otherwise. this is slightly more efficient that
// finding the position of each then comparing them.
grid.lastSelectedItemIndex = index;
}
- grid.informSelection();
+ var selected = grid.getSelectedItems();
+ if (grid.onItemSelected) {
+ if ($scope.selected[index]) {
+ grid.onItemSelected(item, null, selected);
+ } else {
+ grid.onItemSelected(null, item, selected);
+ }
+ }
}
// Builds a sort expression from column sort priorities.
scope[field] = true;
}
);
- scope.template = element.html();
+
+ // any HTML content within the field is its custom template
+ var tmpl = element.html();
+ if (tmpl && !tmpl.match(/^\s*$/))
+ scope.template = tmpl
+
egGridCtrl.columnsProvider.add(scope);
scope.$destroy();
}
return $q.when();
var query = provider.query();
+ console.log(JSON.stringify(query));
if (!query || angular.equals(query, {}))
return $q.when();
var queryFields = {}
angular.forEach(provider.columnsProvider.columns,
function(col) {
- if (col.required || col.visible)
+ // only query IDL-tracked columns
+ if (col.datatype && (col.required || col.visible))
queryFields[col.name] = col.path;
}
);
case 'timestamp':
// canned angular date filter FTW
return $filter('date')(value, 'shortDate');
+ case 'money':
+ return $filter('currency')(value);
default:
return value;
}