</actions>
</permacrud>
</class>
- <class id="mbts" controller="open-ils.cstore" oils_obj:fieldmapper="money::billable_transaction_summary" oils_persist:tablename="money.materialized_billable_xact_summary" reporter:label="Billable Transaction Summary" oils_persist:readonly="true">
+ <class id="mbts" controller="open-ils.cstore open-ils.pcrud" oils_obj:fieldmapper="money::billable_transaction_summary" oils_persist:tablename="money.materialized_billable_xact_summary" reporter:label="Billable Transaction Summary" oils_persist:readonly="true">
<fields oils_persist:primary="id" oils_persist:sequence="">
<field reporter:label="Balance Owed" name="balance_owed" reporter:datatype="money"/>
<field reporter:label="Transaction ID" name="id" reporter:datatype="id"/>
<links>
<link field="usr" reltype="has_a" key="id" map="" class="au"/>
</links>
+ <permacrud xmlns="http://open-ils.org/spec/opensrf/IDL/permacrud/v1">
+ <actions>
+ <retrieve permission="VIEW_USER_TRANSACTIONS">
+ <context link="usr" field="home_ou" />
+ </retrieve>
+ </actions>
+ </permacrud>
</class>
<class id="mbtslv" controller="open-ils.cstore" oils_obj:fieldmapper="money::billable_transaction_summary_location_view" oils_persist:tablename="money.billable_xact_summary_location_view" reporter:label="Billable Transaction Summary with Billing Location" oils_persist:readonly="true" reporter:core="true">
<fields oils_persist:primary="id" oils_persist:sequence="">
</actions>
</permacrud>
</class>
- <class id="mbt" controller="open-ils.cstore" oils_obj:fieldmapper="money::billable_transaction" oils_persist:tablename="money.billable_xact" reporter:label="Billable Transaction">
+ <class id="mbt" controller="open-ils.cstore open-ils.pcrud" oils_obj:fieldmapper="money::billable_transaction" oils_persist:tablename="money.billable_xact" reporter:label="Billable Transaction">
<fields oils_persist:primary="id" oils_persist:sequence="money.billable_xact_id_seq">
<field reporter:label="Transaction ID" name="id" reporter:datatype="id" />
<field reporter:label="User" name="usr" reporter:datatype="link"/>
<link field="payment_total" reltype="might_have" key="xact" map="" class="rxpt"/>
<link field="summary" reltype="might_have" key="id" map="" class="mbts"/>
</links>
+ <permacrud xmlns="http://open-ils.org/spec/opensrf/IDL/permacrud/v1">
+ <actions>
+ <retrieve permission="VIEW_USER_TRANSACTIONS">
+ <context link="usr" field="home_ou" />
+ </retrieve>
+ </actions>
+ </permacrud>
</class>
<class id="actsce" controller="open-ils.cstore" oils_obj:fieldmapper="actor::stat_cat_entry" oils_persist:tablename="actor.stat_cat_entry" reporter:label="User Stat Cat Entry">
<fields oils_persist:primary="id" oils_persist:sequence="actor.stat_cat_entry_id_seq">
--- /dev/null
+<h2>[% l('Bill History') %]</h2>
+
+<ul class="nav nav-tabs">
+ <li ng-class="{active : bill_tab == 'transactions'}">
+ <a href="./circ/patron/{{patron().id()}}/bill_history/transactions">
+ [% l('Transactions') %]
+ </a>
+ </li>
+ <li ng-class="{active : bill_tab == 'payments'}">
+ <a href="./circ/patron/{{patron().id()}}/bill_history/payments">
+ [% l('Payments') %]
+ </a>
+ </li>
+</ul>
+<div class="tab-content">
+ <div class="tab-pane active">
+
+ <div ng-if="bill_tab == 'transactions'">
+ <div class="flex-row padded">
+ <div>[% l('Selected Billed:') %]</div>
+ <div>{{selected_billed | currency}}</div>
+ <div>[% l('Selected Paid:') %]</div>
+ <div>{{selected_paid | currency}}</div>
+ <div class="flex-cell"></div>
+ <div>[% l('Start Date:') %]</div>
+ <div><input eg-date-input class="form-control" ng-model="dates.xact_start"/></div>
+ <div>[% l('End Date:') %]</div>
+ <div><input eg-date-input class="form-control" ng-model="dates.xact_finish"/></div>
+ </div><!-- top row -->
+ <hr/>
+
+ <eg-grid
+ idl-class="mbt"
+ id-field="id"
+ query="xactQuery"
+ activate-item="activateBill"
+ revision="xactRevision">
+
+ <eg-grid-action
+ label="[% l('Add Billing') %]" handler=""></eg-grid-action>
+ <eg-grid-action
+ label="[% l('Full Details') %]" handler="showFullDetails"></eg-grid-action>
+
+ <eg-grid-field path="summary.balance_owed"></eg-grid-field>
+ <eg-grid-field path="id" label="[% l('Bill #') %]"></eg-grid-field>
+ <eg-grid-field path="xact_finish" label="[% l('Finish') %]"></eg-grid-field>
+ <eg-grid-field path="xact_start" label="[% l('Start') %]"></eg-grid-field>
+ <eg-grid-field path="summary.total_owed" label="[% l('Total Billed') %]"></eg-grid-field>
+ <eg-grid-field path="summary.total_paid" label="[% l('Total Paid') %]"></eg-grid-field>
+ <eg-grid-field path="summary.xact_type" label="[% l('Type') %]"></eg-grid-field>
+
+
+ <eg-grid-field label="[% l('Title') %]" name="title"
+ path="circulation.target_copy.call_number.record.simple_record.title">
+ <a href="[% ctx.base_path %]/opac/record/{{item.record_id}}">{{item.title}}</a>
+ </eg-grid-field>
+
+ <!-- needed for bib link -->
+ <eg-grid-field name="record_id"
+ path="circulation.target_copy.call_number.record.id"
+ required hidden></eg-grid-field>
+
+ <eg-grid-field label="[% l('Barcode') %]" name="copy_barcode"
+ path="circulation.target_copy.barcode">
+ <a target="_self" href="./cat/item/{{item.copy_id}}">{{item.copy_barcode}}</a>
+ </eg-grid-field>
+
+ <!-- needed for item link -->
+ <eg-grid-field name="copy_id"
+ path="circulation.target_copy.id" required hidden></eg-grid-field>
+
+ <!-- needed for grid query -->
+ <eg-grid-field path="summary.last_payment_ts" required hidden></eg-grid-field>
+
+ <eg-grid-field path="summary.*" hidden></eg-grid-field>
+ <eg-grid-field path="circulation.target_copy.*" hidden></eg-grid-field>
+ <eg-grid-field path="circulation.target_copy.call_number.*" hidden></eg-grid-field>
+
+ </eg-grid>
+ </div><!-- xacts -->
+
+ </div>
+</div>
+
<div class="col-md-2 strong-text">[% l('Billing Location') %]</div>
<div class="col-md-2">{{xact.billing_location().shortname()}}</div>
<div class="col-md-2 strong-text">[% l('Total Billed') %]</div>
- <div class="col-md-2">{{xact.total_owed() | currency}}</div>
+ <div class="col-md-2">{{xact.summary().total_owed() | currency}}</div>
<div class="col-md-2 strong-text">[% l('Title') %]</div>
<div class="col-md-2">
<a ng-if="title_id" href="[% ctx.base_path %]/opac/record/{{title_id}}">{{title}}</a>
</div>
<div class="row">
<div class="col-md-2 strong-text">[% l('Type') %]</div>
- <div class="col-md-2">{{xact.xact_type()}}</div>
+ <div class="col-md-2">{{xact.summary().xact_type()}}</div>
<div class="col-md-2 strong-text">[% l('Total Paid') %]</div>
- <div class="col-md-2">{{xact.total_paid() | currency}}</div>
+ <div class="col-md-2">{{xact.summary().total_paid() | currency}}</div>
<div class="col-md-2 strong-text">[% l('Checked Out') %]</div>
<div class="col-md-2">{{xact.circulation().xact_start() | date:'short'}}</div>
</div>
<div class="col-md-2 strong-text">[% l('Start') %]</div>
<div class="col-md-2">{{xact.xact_start() | date:'short'}}</div>
<div class="col-md-2 strong-text">[% l('Total Billed') %]</div>
- <div class="col-md-2">{{xact.balance_owed() | currency}}</div>
+ <div class="col-md-2">{{xact.summary().balance_owed() | currency}}</div>
<div class="col-md-2 strong-text">[% l('Due Date') %]</div>
<div class="col-md-2">{{xact.circulation().due_date() | date:'short'}}</div>
</div>
.flex-row {
display: flex;
}
+.flex-row.padded div {
+ padding: 5px;
+}
.flex-cell {
flex: 1;
padding: 4px; /* bootstrap default is much bigger */
<div style="display:flex">
<div style="flex:1" class="eg-grid-column-move-handle">
<div ng-if="col.sortable">
- <a column="{{col.name}}" href=''
+ <a column="{{col.name}}" href
eg-grid-column-drag-source
ng-click="quickSort(col.name)">{{col.label}}</a>
</div>
resolve : resolver
});
+ $routeProvider.when('/circ/patron/:id/bill_history/:history_tab', {
+ templateUrl: './circ/patron/t_bill_history',
+ controller: 'BillHistoryCtrl',
+ resolve : resolver
+ });
+
$routeProvider.when('/circ/patron/:id/messages', {
templateUrl: './circ/patron/t_messages',
controller: 'PatronMessagesCtrl',
});
}
+ 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('mobts', id,
- { flesh : 5,
- flesh_fields : {
- mobts : ['circulation','grocery'],
- circ : ['target_copy'],
- acp : ['call_number','location','status','age_protect'],
- acn : ['record'],
- bre : ['simple_record']
- },
- select : { bre : ['id'] } // avoid MARC
- },
- {authoritative : true}
+ return egCore.pcrud.retrieve(
+ 'mbt', id, service.xactFlesh, {authoritative : true}
).then(function(xact) {
- xact.billing_location(egCore.org.get(xact.billing_location()));
+
+ /* mobts has 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;
});
}
billSvc.fetchSummary().then(function(s) {$scope.summary = s});
})
}
+
+ $scope.showHistory = function() {
+ $location.path('/circ/patron/' +
+ patronSvc.current.id() + '/bill_history/transactions');
+ }
// For now, only adds billing to first selected item.
// Could do batches later if needed
);
}
- $scope.showHistory = function() {
- // go to bills/history
- }
-
$scope.selectRefunds = function() {
- // select grid items where refunds are due
+ // TODO: select grid items where refunds are due
}
$scope.printBills = function() {
- // print selected bills using the bills print template
+ // TODO: print selected bills using the bills print template
}
$scope.applyPayment = function() {
}])
+.controller('BillHistoryCtrl',
+ ['$scope','$q','$routeParams','egCore','patronSvc','billSvc','egPromptDialog','$location',
+function($scope, $q , $routeParams , egCore , patronSvc , billSvc , egPromptDialog , $location) {
+
+ $scope.initTab('bills', $routeParams.id);
+ billSvc.userId = $routeParams.id;
+ $scope.bill_tab = $routeParams.history_tab;
+ $scope.selected_billed = 0;
+ $scope.selected_paid = 0;
+
+ var start = new Date(); // now - 1 year
+ start.setFullYear(start.getFullYear() - 1),
+ $scope.dates = {
+ xact_start : start,
+ xact_finish : new Date()
+ }
+
+ $scope.xactQuery = function() {
+ // strip the time for our search purposes
+ var start = $scope.dates.xact_start.toISOString().replace(/T.*/,'');
+ var end = $scope.dates.xact_finish.toISOString().replace(/T.*/,'');
+ var today = new Date().toISOString().replace(/T.*/,'');
+
+ // open-ils.actor.user.transactions.history.have_bill_or_payment
+ var query = {
+ '-or' : [
+ {'summary.balance_owed' : {'<>' : 0}},
+ {'summary.last_payment_ts' : {'<>' : null}}
+ ],
+ xact_start : {'>=' : start},
+ usr : billSvc.userId
+ }
+
+ // end date of today implies that xacts with a null xact_finish
+ // are also acceptable
+ if (end < today) query.xact_finish = {'<=' : end};
+
+ return query;
+ }
+
+ $scope.showFullDetails = function(all) {
+ if (all[0])
+ $location.path('/circ/patron/' +
+ patronSvc.current.id() + '/bill/' + all[0].id);
+ }
+ $scope.activateBill = function(xact) {
+ $scope.showFullDetails([xact]);
+ }
+
+}])
+
+
oncomplete : function() {
deferred.resolve(lastResp);
},
+ onmethoderror : function(e) {
+ self.err(method + " failed " + e + "\n" + js2JSON(params));
+ deferred.reject(e);
+ },
onerror : function(e) {
- self.err(method + " failed " + e);
+ self.err(method + " failed " + e + "\n" + js2JSON(params));
deferred.reject(e);
}
}).send();
}
})
+
+/*
+http://stackoverflow.com/questions/18061757/angular-js-and-html5-date-input-value-how-to-get-firefox-to-show-a-readable-d
+
+This directive allows us to use html5 input type="date" (for Chrome) and
+gracefully fall back to a regular ISO text input for Firefox.
+It also allows us to abstract away some browser finickiness.
+*/
+.directive(
+ 'egDateInput',
+ function(dateFilter) {
+ return {
+ require: 'ngModel',
+ template: '<input type="date"></input>',
+ replace: true,
+ link: function(scope, elm, attrs, ngModelCtrl) {
+ ngModelCtrl.$formatters.unshift(function (modelValue) {
+ return dateFilter(modelValue, 'yyyy-MM-dd');
+ });
+
+ ngModelCtrl.$parsers.unshift(function(viewValue) {
+ return new Date(viewValue);
+ });
+ },
+ };
+})