<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/user.js"></script>
+<script src="[% ctx.media_prefix %]/js/ui/default/staff/circ/services/billing.js"></script>
<script src="[% ctx.media_prefix %]/js/ui/default/staff/circ/patron/app.js"></script>
<!-- load the rest on demand? -->
revision="xactRevision">
<eg-grid-action
- label="[% l('Add Billing') %]" handler=""></eg-grid-action>
+ label="[% l('Add Billing') %]" handler="addBilling"></eg-grid-action>
<eg-grid-action
label="[% l('Full Details') %]" handler="showFullDetails"></eg-grid-action>
+++ /dev/null
-<!-- edit bucket dialog -->
-<form ng-submit="ok(billArgs)" role="form" class="form-horizontal">
- <div class="modal-content">
- <div class="modal-header">
- <button type="button" class="close"
- ng-click="cancel()" aria-hidden="true">×</button>
- <h4 class="modal-title">
- [% l('Bill Patron: [_1], [_2] [_3] : [_4]',
- '{{patron.family_name()}}',
- '{{patron.first_given_name()}}',
- '{{patron.second_given_name()}}',
- '{{patron.card().barcode()}}') %]
- </h4>
-
- <div ng-if="xact">
- <hr/>
- <div class="row">
- <div class="col-md-3">[% l('Bill #') %]</div>
- <div class="col-md-3">{{xact.id}}</div>
- <div class="col-md-3">[% l('Total Billed') %]</div>
- <div class="col-md-3">{{xact.total_owed | currency}}</div>
- </div>
- <div class="row">
- <div class="col-md-3">[% l('Type') %]</div>
- <div class="col-md-3">{{xact.xact_type}}</div>
- <div class="col-md-3">[% l('Total Paid') %]</div>
- <div class="col-md-3">{{xact.total_paid | currency}}</div>
- </div>
- <div class="row">
- <div class="col-md-3">[% l('Start') %]</div>
- <div class="col-md-3">{{xact.xact_start | date:'short'}}</div>
- <div class="col-md-3">[% l('Total Billed') %]</div>
- <div class="col-md-3">{{xact.balance_owed | currency}}</div>
- </div>
- <div class="row">
- <div class="col-md-3">[% l('Finish') %]</div>
- <div class="col-md-3">{{xact.xact_finish | date:'short'}}</div>
- <div class="col-md-3">[% l('Renewal?') %]</div>
- <div class="col-md-3">
- <span ng-if="xact.circulation.desk_renewal == 't'">[% l('Desk') %]</span>
- <span ng-if="xact.circulation.phone_renewal == 't'">[% l('Phone') %]</span>
- <span ng-if="xact.circulation.opac_renewal == 't'">[% l('OPAC') %]</span>
- </div>
- </div>
- </div>
- </div>
- <div class="modal-body">
- <div class="form-group">
- <label for="bill-dialog-location" class="control-label col-md-4">
- [% l('Location:') %]
- </label>
- <div class="col-md-8">
- <p class="form-control-static">{{location.shortname()}}</p>
- </div>
- </div>
-
- <div class="form-group">
- <label for="bill-dialog-type" class="control-label col-md-4">
- [% l('Billing Type:') %]
- </label>
- <div class="col-md-8">
- <select ng-model="billArgs.billingType" class="form-control"
- ng-change="updateDefaultPrice()">
- <option ng-repeat="type in billingTypes" value="{{type.id()}}">
- {{type.name()}}
- </option>
- </select>
- </div>
- </div>
- <div class="form-group">
- <label for="bill-dialog-amount" class="control-label col-md-4">[% l('Amount:') %]</label>
- <div class="col-md-8">
- <input type="number" min="0" step="any" class="form-control"
- focus-me='focus' required id="bill-dialog-amount"
- ng-model="billArgs.amount"/>
- </div>
- </div>
- <div class="form-group">
- <label for="bill-dialog-note" class="control-label col-md-4">[% l('Note:') %]</label>
- <div class="col-md-8">
- <textarea rows="3" class="form-control" placeholder="[% l('Note...') %]"
- id="bill-dialog-note" ng-model="billArgs.note"></textarea>
- </div>
- </div>
- </div>
- <div class="modal-footer">
- <input type="submit" class="btn btn-success" value="[% l('Submit Bill') %]"/>
- <button class="btn btn-warning" ng-click="cancel($event)">[% l('Cancel') %]</button>
- </div>
- </div>
-</form>
-
-
--- /dev/null
+<!-- edit bucket dialog -->
+<form ng-submit="ok(billArgs)" role="form" class="form-horizontal">
+ <div class="modal-content">
+ <div class="modal-header">
+ <button type="button" class="close"
+ ng-click="cancel()" aria-hidden="true">×</button>
+ <h4 class="modal-title">
+ [% l('Bill Patron: [_1], [_2] [_3] : [_4]',
+ '{{patron.family_name()}}',
+ '{{patron.first_given_name()}}',
+ '{{patron.second_given_name()}}',
+ '{{patron.card().barcode()}}') %]
+ </h4>
+
+ <div ng-if="xact">
+ <hr/>
+ <div class="row">
+ <div class="col-md-3">[% l('Bill #') %]</div>
+ <div class="col-md-3">{{xact.id}}</div>
+ <div class="col-md-3">[% l('Total Billed') %]</div>
+ <div class="col-md-3">{{xact.summary.total_owed | currency}}</div>
+ </div>
+ <div class="row">
+ <div class="col-md-3">[% l('Type') %]</div>
+ <div class="col-md-3">{{xact.summary.xact_type}}</div>
+ <div class="col-md-3">[% l('Total Paid') %]</div>
+ <div class="col-md-3">{{xact.summary.total_paid | currency}}</div>
+ </div>
+ <div class="row">
+ <div class="col-md-3">[% l('Start') %]</div>
+ <div class="col-md-3">{{xact.xact_start | date:'short'}}</div>
+ <div class="col-md-3">[% l('Total Billed') %]</div>
+ <div class="col-md-3">{{xact.summary.balance_owed | currency}}</div>
+ </div>
+ <div class="row">
+ <div class="col-md-3">[% l('Finish') %]</div>
+ <div class="col-md-3">{{xact.xact_finish | date:'short'}}</div>
+ <div class="col-md-3">[% l('Renewal?') %]</div>
+ <div class="col-md-3">
+ <span ng-if="xact.circulation.desk_renewal == 't'">[% l('Desk') %]</span>
+ <span ng-if="xact.circulation.phone_renewal == 't'">[% l('Phone') %]</span>
+ <span ng-if="xact.circulation.opac_renewal == 't'">[% l('OPAC') %]</span>
+ </div>
+ </div>
+ </div>
+ </div>
+ <div class="modal-body">
+ <div class="form-group">
+ <label for="bill-dialog-location" class="control-label col-md-4">
+ [% l('Location:') %]
+ </label>
+ <div class="col-md-8">
+ <p class="form-control-static">{{location.shortname()}}</p>
+ </div>
+ </div>
+
+ <div class="form-group">
+ <label for="bill-dialog-type" class="control-label col-md-4">
+ [% l('Billing Type:') %]
+ </label>
+ <div class="col-md-8">
+ <select ng-model="billArgs.billingType" class="form-control"
+ ng-change="updateDefaultPrice()">
+ <option ng-repeat="type in billingTypes" value="{{type.id()}}">
+ {{type.name()}}
+ </option>
+ </select>
+ </div>
+ </div>
+ <div class="form-group">
+ <label for="bill-dialog-amount" class="control-label col-md-4">[% l('Amount:') %]</label>
+ <div class="col-md-8">
+ <input type="number" min="0" step="any" class="form-control"
+ focus-me='focus' required id="bill-dialog-amount"
+ ng-model="billArgs.amount"/>
+ </div>
+ </div>
+ <div class="form-group">
+ <label for="bill-dialog-note" class="control-label col-md-4">[% l('Note:') %]</label>
+ <div class="col-md-8">
+ <textarea rows="3" class="form-control" placeholder="[% l('Note...') %]"
+ id="bill-dialog-note" ng-model="billArgs.note"></textarea>
+ </div>
+ </div>
+ </div>
+ <div class="modal-footer">
+ <input type="submit" class="btn btn-success" value="[% l('Submit Bill') %]"/>
+ <button class="btn btn-warning" ng-click="cancel($event)">[% l('Cancel') %]</button>
+ </div>
+ </div>
+</form>
+
+
});
}
- service.fetchBillingTypes = function() {
- if (egCore.env.cbt)
- return $q.when(egCore.env.cbt.list);
-
- return egCore.pcrud.search('cbt',
- { // first 100 are reserved for system-generated bills
- id : {'>' : 100},
- owner : egCore.org.ancestors(
- egCore.auth.user().ws_ou(), true)
- },
- {}, {atomic : true}
- ).then(function(list) {
- egCore.env.absorbList(list, 'cbt');
- return list;
- });
- }
-
- service.createGroceryXact = function(args) {
- var groc = new egCore.idl.mg();
- groc.billing_location(egCore.auth.user().ws_ou());
- groc.note(args.note);
- groc.usr(patronSvc.current.id());
-
- // create the xact
- return egCore.net.request(
- 'open-ils.circ',
- 'open-ils.circ.money.grocery.create',
- egCore.auth.token(), groc
-
- // create the billing on the new xact
- ).then(function(xact_id) {
- if (evt = egCore.evt.parse(xact_id))
- return alert(evt);
- return xact_id;
- });
- }
-
- service.createBilling = function(xact_id, args) {
- var bill = new egCore.idl.mb();
- bill.xact(xact_id);
- bill.amount(args.amount);
- bill.btype(args.billingType);
- bill.billing_type(egCore.env.cbt.map[args.billingType].name());
- bill.note(args.note);
-
- return egCore.net.request(
- 'open-ils.circ',
- 'open-ils.circ.money.billing.create',
- egCore.auth.token(), bill
-
- // check the billing response
- ).then(function(bill_id) {
- if (evt = egCore.evt.parse(bill_id)) {
- alert(evt);
- } else {
- return bill_id;
- }
- });
- }
-
- service.billPatron = function(args, xact) {
-
- // apply a billing to an existing transaction
- if (xact) return service.createBilling(xact.id, args);
-
- // create a new grocery xact, then apply a billing
- return service.createGroceryXact(args)
- .then(function(xact_id) {
- return service.createBilling(xact_id, args);
- });
- }
-
- service.xactFlesh = {
- flesh : 5,
- flesh_fields : {
- mbt : ['summary','circulation','grocery','reservation'],
- circ : ['target_copy'],
- acp : ['call_number','location','status','age_protect'],
- acn : ['record'],
- bre : ['simple_record']
- },
- select : { bre : ['id'] } // avoid MARC
- }
-
- service.fetchXact = function(id) {
- return egCore.pcrud.retrieve(
- 'mbt', id, service.xactFlesh, {authoritative : true}
- ).then(function(xact) {
-
- /* mobts has billing_location, but mbts does not
- * only the xact detail page shows it now, so maybe move
- * this into the template...
- *
- var loc;
- if (xact.circulation()) loc = xact.circulation().circ_lib();
- if (xact.reservation()) loc = xact.reservation().pickup_lib();
- if (xact.grocery()) loc = xact.grocery().billing_location();
- xact.billing_location(egCore.org.get(loc));
- */
- return xact;
- });
- }
-
service.fetchBills = function(xact_id) {
var bills = [];
return egCore.pcrud.search('mb',
.controller('PatronBillsCtrl',
['$scope','$q','$routeParams','egCore','egConfirmDialog','$location',
'egGridDataProvider','billSvc','patronSvc','egPromptDialog','$modal',
+ 'egBilling',
function($scope , $q , $routeParams , egCore , egConfirmDialog , $location,
- egGridDataProvider , billSvc , patronSvc , egPromptDialog , $modal) {
+ egGridDataProvider , billSvc , patronSvc , egPromptDialog , $modal,
+ egBilling) {
$scope.initTab('bills', $routeParams.id);
billSvc.userId = $routeParams.id;
// For now, only adds billing to first selected item.
// Could do batches later if needed
$scope.addBilling = function(all) {
- if (all[0]) showBillDialog(all[0]);
+ if (all[0]) {
+ egBilling.showBillDialog({
+ xact : egCore.idl.flatToNestedHash(all[0]),
+ patron : $scope.patron()
+ }).then(refreshDisplay);
+ }
}
$scope.showBillDialog = function($event) {
- showBillDialog();
- }
-
- function showBillDialog(xact) {
- return $modal.open({
- templateUrl: './circ/patron/t_bill_patron_dialog',
- controller:
- ['$scope','$modalInstance','$timeout','billingTypes',
- function($scope , $modalInstance , $timeout , billingTypes) {
- $scope.focus = true;
- $scope.xact = xact;
- $scope.patron = patronSvc.current;
- $scope.billingTypes = billingTypes;
- $scope.location = egCore.org.get(egCore.auth.user().ws_ou()),
- $scope.billArgs = {
- billingType : 101 // default to stock Misc. billing type
- }
- $scope.ok = function(args) { $modalInstance.close(args) }
- $scope.cancel = function () { $modalInstance.dismiss() }
- $scope.updateDefaultPrice = function() {
- var type = billingTypes.filter(function(t) {
- return t.id() == $scope.billArgs.billingType })[0];
- if (type.default_price() && !$scope.billArgs.amount)
- $scope.billArgs.amount = Number(type.default_price());
- }
- }],
- resolve : {
- // if we don't already have them, fetch the billing types
- billingTypes : function() {
- return billSvc.fetchBillingTypes();
- }
- }
- }).result.then(
- function(args) {
- // send the billing to the server using the arguments
- // provided in the billing dialog, then refresh
- billSvc.billPatron(args, xact).then(refreshDisplay);
- }
- );
+ egBilling.showBillDialog({
+ patron : $scope.patron()
+ }).then(refreshDisplay);
}
// Select refunds adds all refunds to the existing selection.
* Displays details of a single transaction
*/
.controller('XactDetailsCtrl',
- ['$scope','$q','$routeParams','egCore','egGridDataProvider','patronSvc','billSvc','egPromptDialog',
-function($scope, $q , $routeParams , egCore , egGridDataProvider , patronSvc , billSvc , egPromptDialog) {
+ ['$scope','$q','$routeParams','egCore','egGridDataProvider','patronSvc','billSvc','egPromptDialog','egBilling',
+function($scope, $q , $routeParams , egCore , egGridDataProvider , patronSvc , billSvc , egPromptDialog , egBilling) {
$scope.initTab('bills', $routeParams.id);
var xact_id = $routeParams.xact_id;
// note: no need to update payments
patronSvc.fetchUserStats();
- billSvc.fetchXact(xact_id).then(function(xact) {
+ egBilling.fetchXact(xact_id).then(function(xact) {
$scope.xact = xact
});
}
// -- retrieve our data
- billSvc.fetchXact(xact_id).then(function(xact) {
+ egBilling.fetchXact(xact_id).then(function(xact) {
$scope.xact = xact;
// set the title. only needs to be done on initial page load
.controller('BillXactHistoryCtrl',
- ['$scope','$q','egCore','patronSvc','billSvc','egPromptDialog','$location',
-function($scope, $q , egCore , patronSvc , billSvc , egPromptDialog , $location) {
+ ['$scope','$q','egCore','patronSvc','billSvc','egPromptDialog','$location','egBilling',
+function($scope, $q , egCore , patronSvc , billSvc , egPromptDialog , $location , egBilling) {
$scope.gridControls = {
selectedItems : function(){return []},
$scope.showFullDetails([item]);
}
}
+ $scope.xactRevision = 0;
// TODO; move me to service
function selected_payment_info() {
$location.path('/circ/patron/' +
patronSvc.current.id() + '/bill/' + all[0].id);
}
+
+ // For now, only adds billing to first selected item.
+ // Could do batches later if needed
+ $scope.addBilling = function(all) {
+ if (all[0]) {
+ egBilling.showBillDialog({
+ xact : egCore.idl.flatToNestedHash(all[0]),
+ patron : $scope.patron()
+ }).then(function() { $scope.xactRevision++; })
+ }
+ }
+
}])
.controller('BillPaymentHistoryCtrl',
--- /dev/null
+/**
+ * Shared services for patron billing.
+ *
+ */
+
+angular.module('egCoreMod')
+
+.factory('egBilling',
+ ['$modal','$q','egCore','egUser',
+function($modal , $q , egCore , egUser) {
+
+ var service = {};
+
+ service.fetchXact = function(xact_id) {
+ return egCore.pcrud.retrieve('mbt', {
+ flesh : 5,
+ flesh_fields : {
+ mbt : ['summary','circulation','grocery','reservation'],
+ circ: ['target_copy'],
+ acp : ['call_number','location','status','age_protect'],
+ acn : ['record'],
+ bre : ['simple_record']
+ },
+ select : {bre : ['id']}}, // avoid MARC
+ {authoritative : true}
+ );
+ }
+
+ service.billPatron = function(args, xact) {
+ // apply a billing to an existing transaction
+ if (xact) return service.createBilling(xact.id, args);
+
+ // create a new grocery xact, then apply a billing
+ return service.createGroceryXact(args)
+ .then(function(xact_id) {
+ return service.createBilling(xact_id, args);
+ });
+ }
+
+ service.createGroceryXact = function(args) {
+ var groc = new egCore.idl.mg();
+ groc.billing_location(egCore.auth.user().ws_ou());
+ groc.note(args.note);
+ groc.usr(args.patron_id);
+
+ // create the xact
+ return egCore.net.request(
+ 'open-ils.circ',
+ 'open-ils.circ.money.grocery.create',
+ egCore.auth.token(), groc
+
+ // create the billing on the new xact
+ ).then(function(xact_id) {
+ if (evt = egCore.evt.parse(xact_id))
+ return alert(evt);
+ return xact_id;
+ });
+ }
+
+ service.fetchBillingTypes = function() {
+ if (egCore.env.cbt)
+ return $q.when(egCore.env.cbt.list);
+
+ return egCore.pcrud.search('cbt',
+ { // first 100 are reserved for system-generated bills
+ id : {'>' : 100},
+ owner : egCore.org.ancestors(
+ egCore.auth.user().ws_ou(), true)
+ },
+ {}, {atomic : true}
+ ).then(function(list) {
+ egCore.env.absorbList(list, 'cbt');
+ return list;
+ });
+ }
+
+ service.createBilling = function(xact_id, args) {
+ var bill = new egCore.idl.mb();
+ bill.xact(xact_id);
+ bill.amount(args.amount);
+ bill.btype(args.billingType);
+ bill.billing_type(egCore.env.cbt.map[args.billingType].name());
+ bill.note(args.note);
+
+ return egCore.net.request(
+ 'open-ils.circ',
+ 'open-ils.circ.money.billing.create',
+ egCore.auth.token(), bill
+
+ // check the billing response
+ ).then(function(bill_id) {
+ if (evt = egCore.evt.parse(bill_id)) {
+ alert(evt);
+ } else {
+ return bill_id;
+ }
+ });
+ }
+
+
+ // args:
+ // xact OR xact_id : if null, creates a grocery xact
+ // patron OR patron_id
+ service.showBillDialog = function(args) {
+
+ return $modal.open({
+ templateUrl: './circ/share/t_bill_patron_dialog',
+ controller:
+ ['$scope','$modalInstance','$timeout','billingTypes','xact','patron',
+ function($scope , $modalInstance , $timeout , billingTypes , xact , patron) {
+ console.debug('billing patron ' + patron.id());
+ $scope.focus = true;
+ $scope.xact = xact;
+ $scope.patron = patron;
+ $scope.billingTypes = billingTypes;
+ $scope.location = egCore.org.get(egCore.auth.user().ws_ou()),
+ $scope.billArgs = {
+ billingType : 101, // default to stock Misc. billing type
+ xact : xact,
+ patron_id : patron.id()
+ }
+ $scope.ok = function(args) { $modalInstance.close(args) }
+ $scope.cancel = function () { $modalInstance.dismiss() }
+ $scope.updateDefaultPrice = function() {
+ var type = billingTypes.filter(function(t) {
+ return t.id() == $scope.billArgs.billingType })[0];
+ if (type.default_price() && !$scope.billArgs.amount)
+ $scope.billArgs.amount = Number(type.default_price());
+ }
+ }],
+ resolve : {
+ // if we don't already have them, fetch the billing types
+ billingTypes : function() {
+ return service.fetchBillingTypes();
+ },
+
+ xact : function() {
+ if (args.xact) return $q.when(args.xact);
+ if (args.xact_id) return service.fetchXact(args.xact_id);
+ return $q.when();
+ },
+
+ patron : function() {
+ return $q.when(args.patron) || egUser.get(args.patron_id);
+ }
+
+ }
+ }).result.then(
+ function(args) {
+ // send the billing to the server using the arguments
+ // provided in the billing dialog, then refresh
+ return service.billPatron(args, args.xact);
+ }
+ );
+ }
+
+ return service;
+}]);
+
+
+
+