--- /dev/null
+<h3 i18n>Bill History</h3>
+
+<eg-add-billing-dialog [patronId]="patronId" #billingDialog>
+</eg-add-billing-dialog>
+
+
+<ul ngbNav #nav="ngbNav" class="nav-tabs" [(activeId)]="tab">
+ <li ngbNavItem="transactions">
+ <a ngbNavLink i18n>Transactions</a>
+ <ng-template ngbNavContent>
+ <eg-grid idlClass="mbt" #xactsGrid
+ persistKey="circ.patron.billhistory_xacts"
+ (onRowActivate)="showStatement($event)"
+ i18n-toolbarLabel [dataSource]="xactsDataSource" [sortable]="true">
+
+ <eg-grid-toolbar-button i18n-label label="Add Billing"
+ (onClick)="addBilling()"></eg-grid-toolbar-button>
+
+ <eg-grid-toolbar-action
+ i18n-label label="Print Bills" (onClick)="printBills($event)">
+ </eg-grid-toolbar-action>
+
+
+ <eg-grid-toolbar-action label="Edit Note" i18n-label
+ (onClick)="openNoteDialog($event)">
+ </eg-grid-toolbar-action>
+ <eg-grid-toolbar-action label="Void Billings" i18n-label
+ (onClick)="openVoidDialog($event)">
+ </eg-grid-toolbar-action>
+ </eg-grid>
+ </ng-template>
+ </li>
+ <li ngbNavItem="payments">
+ <a ngbNavLink i18n>Payments</a>
+ <ng-template ngbNavContent>
+ </ng-template>
+ </li>
+</ul>
+
+<ng-container><div [ngbNavOutlet]="nav"></div></ng-container>
+
--- /dev/null
+import {Component, Input, OnInit, AfterViewInit, ViewChild} from '@angular/core';
+import {Router, ActivatedRoute, ParamMap} from '@angular/router';
+import {from, empty, range} from 'rxjs';
+import {concatMap, tap, takeLast} from 'rxjs/operators';
+import {NgbNav, NgbNavChangeEvent} from '@ng-bootstrap/ng-bootstrap';
+import {IdlObject, IdlService} from '@eg/core/idl.service';
+import {EventService} from '@eg/core/event.service';
+import {OrgService} from '@eg/core/org.service';
+import {NetService} from '@eg/core/net.service';
+import {PcrudService, PcrudContext} from '@eg/core/pcrud.service';
+import {AuthService} from '@eg/core/auth.service';
+import {ServerStoreService} from '@eg/core/server-store.service';
+import {PatronService} from '@eg/staff/share/patron/patron.service';
+import {PatronContextService, BillGridEntry} from './patron.service';
+import {GridDataSource, GridColumn, GridCellTextGenerator} from '@eg/share/grid/grid';
+import {GridComponent} from '@eg/share/grid/grid.component';
+import {Pager} from '@eg/share/util/pager';
+import {CircService, CircDisplayInfo} from '@eg/staff/share/circ/circ.service';
+import {PrintService} from '@eg/share/print/print.service';
+import {PromptDialogComponent} from '@eg/share/dialog/prompt.component';
+import {AlertDialogComponent} from '@eg/share/dialog/alert.component';
+import {ConfirmDialogComponent} from '@eg/share/dialog/confirm.component';
+import {BillingService} from '@eg/staff/share/billing/billing.service';
+import {AddBillingDialogComponent} from '@eg/staff/share/billing/billing-dialog.component';
+import {AudioService} from '@eg/share/util/audio.service';
+import {ToastService} from '@eg/share/toast/toast.service';
+
+@Component({
+ templateUrl: 'billing-history.component.html',
+ selector: 'eg-patron-billing-history'
+})
+export class BillingHistoryComponent implements OnInit {
+
+ @Input() patronId: number;
+ @Input() tab: string;
+
+ xactsDataSource: GridDataSource = new GridDataSource();
+ paymentsDataSource: GridDataSource = new GridDataSource();
+
+ xactsTextGenerator: GridCellTextGenerator;
+ paymentsTextGenerator: GridCellTextGenerator;
+
+ @ViewChild('xactsGrid') private xactsGrid: GridComponent;
+ @ViewChild('paymentsGrid') private paymentsGrid: GridComponent;
+ @ViewChild('billingDialog') private billingDialog: AddBillingDialogComponent;
+
+ constructor(
+ private router: Router,
+ private route: ActivatedRoute,
+ private org: OrgService,
+ private evt: EventService,
+ private net: NetService,
+ private pcrud: PcrudService,
+ private auth: AuthService,
+ private idl: IdlService,
+ private circ: CircService,
+ private billing: BillingService,
+ private printer: PrintService,
+ public patronService: PatronService,
+ public context: PatronContextService
+ ) {}
+
+ ngOnInit() {
+
+ this.xactsDataSource.getRows = (pager: Pager, sort: any[]) => {
+ const orderBy: any = {};
+ if (sort.length) {
+ orderBy.mb = sort[0].name + ' ' + sort[0].dir;
+ }
+
+ return this.pcrud.search('mbt', {usr: this.patronId}, {
+ order_by: orderBy,
+ join: {
+ mbts: {
+ filter: {
+ '-or': [
+ {balance_owed: {'<>': 0}},
+ {last_payment_ts: {'<>': null}}
+ ]
+ }
+ }
+ }
+ });
+ };
+
+ /*
+ this.paymentsDataSource.getRows = (pager: Pager, sort: any[]) => {
+ const orderBy: any = {};
+ if (sort.length) {
+ orderBy.mp = sort[0].name + ' ' + sort[0].dir;
+ }
+ return this.pcrud.search(
+ 'mp', {xact: this.xactId}, {order_by: orderBy});
+ };
+ */
+ }
+
+ showStatement(row: BillGridEntry) {
+ this.router.navigate(['/staff/circ/patron',
+ this.patronId, 'bills', row.xact.id(), 'statement']);
+ }
+
+ addBillingForXact(rows: BillGridEntry[]) {
+ if (rows.length === 0) { return; }
+ const xactIds = rows.map(r => r.xact.id());
+
+ this.billingDialog.newXact = false;
+ let changesApplied = false;
+
+ from(xactIds)
+ .pipe(concatMap(id => {
+ this.billingDialog.xactId = id;
+ return this.billingDialog.open();
+ }))
+ .pipe(tap(data => {
+ if (data) {
+ changesApplied = true;
+ }
+ }))
+ .subscribe(null, null, () => {
+ if (changesApplied) {
+ this.xactsGrid.reload();
+ }
+ });
+ }
+
+ printBills(rows: BillGridEntry[]) {
+ if (rows.length === 0) { return; }
+
+ this.printer.print({
+ templateName: 'bills_historical',
+ contextData: {xacts: rows.map(r => r.xact)},
+ printContext: 'default'
+ });
+ }
+}
+
+
params => [
{desc => 'Authentication token', type => 'string'},
{desc => 'User ID', type => 'number'},
+ {desc => q/Options: {
+ xact_ids: load specific transactions
+ have_balance:
+ have_charge:
+ }/, type => 'object'},
{desc => 'Xact IDs. Optionally limit to specific transactions',
type => 'array'}
],
);
sub user_billing_xacts {
- my ($self, $client, $auth, $user_id, $xact_ids) = @_;
+ my ($self, $client, $auth, $user_id, $options) = @_;
+
+ $options ||= {};
+ my $xact_ids = $options->{xact_ids};
+ my $have_balance = $options->{have_balance};
+ my $have_charge = $options->{have_charge};
+ my $have_payment = $options->{have_payment};
my $e = new_editor(authtoken => $auth, xact => 1);
return $e->die_event unless $e->checkauth;
# Start with the user summary.
$client->respond($e->retrieve_money_open_with_balance_user_summary($user_id));
+ my $where = {};
+ if ($xact_ids) { $where->{id} = $xact_ids; }
+ if ($have_balance) { $where->{balance_owed} = {'<>' => 0}; }
+ if ($have_charge) { $where->{last_billing_ts} = {'<>' => undef}; }
+ if ($have_payment) { $where->{last_payment_ts} = {'<>' => undef}; }
+
# Even if xact_ids are specified, run this query to confirm the
# provided IDs are linked to the specified user and have a balance.
$xact_ids = $e->json_query({