--- /dev/null
+
+<h3 i18n>Transaction: #{{xactId}}</h3>
+
+<div *ngIf="entry" class="striped-rows-odd">
+ <div class="row p-1">
+ <div class="col-lg-2" i18n>Billing Location</div>
+ <div class="col-lg-2">{{entry.billingLocation}}</div>
+ <div class="col-lg-2" i18n>Total Billed</div>
+ <div class="col-lg-1">{{entry.xact.summary().total_owed() | currency}}</div>
+ <div class="col-lg-2" i18n>Title</div>
+ <div class="col-lg-3">{{entry.title}}</div>
+ </div>
+ <div class="row mt-1 p-1">
+ <div class="col-lg-2" i18n>Type</div>
+ <div class="col-lg-2"[ngSwitch]="entry.xact.summary().xact_type()">
+ <span *ngSwitchCase="'circulation'" i18n>Circulation</span>
+ <span *ngSwitchCase="'grocery'" i18n>Grocery</span>
+ </div>
+ <div class="col-lg-2" i18n>Total Paid / Credited</div>
+ <div class="col-lg-1">{{entry.xact.summary().total_paid() | currency}}</div>
+ <div class="col-lg-2" i18n>Checked Out</div>
+ <div class="col-lg-3">
+ <ng-container *ngIf="entry.xact.circulation()">
+ {{entry.xact.xact_start() | date:'short'}}
+ </ng-container>
+ </div>
+ </div>
+ <div class="row mt-1 p-1">
+ <div class="col-lg-2" i18n>Started</div>
+ <div class="col-lg-2">{{entry.xact.xact_start() | date:'short'}}</div>
+ <div class="col-lg-2" i18n>Balance Due</div>
+ <div class="col-lg-1">{{entry.xact.summary().balance_owed() | currency}}</div>
+ <div class="col-lg-2" i18n>Due Date</div>
+ <div class="col-lg-3">
+ <ng-container *ngIf="entry.xact.circulation()">
+ {{entry.xact.circulation().due_date() | date:'short'}}
+ </ng-container>
+ </div>
+ </div>
+ <div class="row mt-1 p-1">
+ <div class="col-lg-2" i18n>Finished</div>
+ <div class="col-lg-2">{{entry.xact.xact_finish() | date:'short'}}</div>
+ <div class="col-lg-2" i18n>Renewal?</div>
+ <div class="col-lg-1">
+ <ng-container *ngIf="entry.xact.circulation()">
+ <eg-bool [value]="entry.xact.circulation().parent_circ() != null">
+ </eg-bool>
+ </ng-container>
+ </div>
+ <div class="col-lg-2" i18n>Checked In</div>
+ <div class="col-lg-3">
+ <ng-container *ngIf="entry.xact.circulation()">
+ {{entry.xact.circulation().checkin_time() | date:'short'}}
+ </ng-container>
+ </div>
+ </div>
+</div>
+
+<hr class="p-2 m-2"/>
+
+<ul ngbNav #statementNav="ngbNav" class="nav-tabs"
+ [activeId]="statementTab" (navChange)="beforeTabChange($event)">
+ <li ngbNavItem="statement">
+ <a ngbNavLink i18n>Statement</a>
+ <ng-template ngbNavContent>
+ </ng-template>
+ </li>
+ <li ngbNavItem="details">
+ <a ngbNavLink i18n>Details</a>
+ <ng-template ngbNavContent>
+ </ng-template>
+ </li>
+</ul>
+
+<div [ngbNavOutlet]="statementNav"></div>
+
+<!--
+Billing LocationBR1Total Billed$4.56Title
+TypegroceryTotal Paid/Credited$2.50Checked Out
+Start3/11/2021 1:34 PMBalance Due$2.06Due Date
+FinishRenewal?Checked In
+-->
--- /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} 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 {CreditCardDialogComponent
+ } from '@eg/staff/share/billing/credit-card-dialog.component';
+import {BillingService, CreditCardPaymentParams} 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: 'bill-statement.component.html',
+ selector: 'eg-patron-bill-statement'
+})
+export class BillStatementComponent implements OnInit {
+
+ @Input() patronId: number;
+ @Input() xactId: number;
+ entry: BillGridEntry;
+ summary: IdlObject;
+ statementTab = 'statement';
+ gridDataSource: GridDataSource = new GridDataSource();
+ cellTextGenerator: GridCellTextGenerator;
+
+ //@ViewChild('grid') private billGrid: GridComponent;
+
+ constructor(
+ private router: Router,
+ private route: ActivatedRoute,
+ private audio: AudioService,
+ private toast: ToastService,
+ private org: OrgService,
+ private evt: EventService,
+ private net: NetService,
+ private pcrud: PcrudService,
+ private auth: AuthService,
+ private printer: PrintService,
+ private serverStore: ServerStoreService,
+ private circ: CircService,
+ private billing: BillingService,
+ public patronService: PatronService,
+ public context: PatronContextService
+ ) {}
+
+ ngOnInit() {
+
+ this.cellTextGenerator = {
+ };
+
+ this.gridDataSource.getRows = (pager: Pager, sort: any[]) => {
+ return empty();
+ };
+
+ this.summary = null;
+ return this.net.request(
+ 'open-ils.actor',
+ 'open-ils.actor.user.transactions.for_billing',
+ this.auth.token(), this.patronId, [this.xactId]
+ ).subscribe(resp => {
+ const evt = this.evt.parse(resp);
+
+ if (evt) {
+ console.error(evt);
+ alert(evt);
+ return;
+ }
+
+ if (!this.summary) {
+ this.summary = resp;
+ } else {
+ this.entry = this.context.formatXactForDisplay(resp);
+ }
+ });
+ }
+}
+
</ng-template>
<eg-grid #billGrid [dataSource]="gridDataSource"
- [sortable]="true" [useLocalSort]="true"
+ [sortable]="true" [useLocalSort]="true" (onRowActivate)="showStatement($event)"
[cellTextGenerator]="cellTextGenerator">
<eg-grid-toolbar-button i18n-label label="Add Billing"
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} from './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 {AudioService} from '@eg/share/util/audio.service';
import {ToastService} from '@eg/share/toast/toast.service';
-interface BillGridEntry extends CircDisplayInfo {
- xact: IdlObject // mbt
- billingLocation?: string;
- paymentPending?: number;
-}
-
@Component({
templateUrl: 'bills.component.html',
selector: 'eg-patron-bills',
}
if (!refreshXacts) {
- this.entries.push(this.formatForDisplay(resp));
+ this.entries.push(this.context.formatXactForDisplay(resp));
return;
}
if (idx < this.entries.length) {
// Update the existing entry
- this.entries[idx] = this.formatForDisplay(resp);
+ this.entries[idx] = this.context.formatXactForDisplay(resp);
} else {
// Adding a new transaction (e.g. from new billing)
- this.entries.push(this.formatForDisplay(resp));
+ this.entries.push(this.context.formatXactForDisplay(resp));
}
})).toPromise()
});
}
- formatForDisplay(xact: IdlObject): BillGridEntry {
-
- const entry: BillGridEntry = {
- xact: xact,
- paymentPending: 0
- };
-
- if (xact.summary().xact_type() !== 'circulation') {
-
- entry.xact.grocery().billing_location(
- this.org.get(entry.xact.grocery().billing_location()));
-
- entry.title = xact.summary().last_billing_type();
- entry.billingLocation =
- xact.grocery().billing_location().shortname();
- return entry;
- }
-
- entry.xact.circulation().circ_lib(
- this.org.get(entry.xact.circulation().circ_lib()));
-
- const circDisplay: CircDisplayInfo =
- this.circ.getDisplayInfo(xact.circulation());
-
- entry.billingLocation =
- xact.circulation().circ_lib().shortname();
-
- return Object.assign(entry, circDisplay);
- }
-
patron(): IdlObject {
return this.context.patron;
}
this.paymentAmount = null;
});
}
+
+ showStatement(row: BillGridEntry) {
+ this.router.navigate(['/staff/circ/patron',
+ this.patronId, 'bills', row.xact.id(), 'statement']);
+ }
}
</span>
</a>
<ng-template ngbNavContent>
- <eg-patron-bills [patronId]="patronId"></eg-patron-bills>
+ <ng-container *ngIf="statementXact">
+ <eg-patron-bill-statement [patronId]="patronId" [xactId]="statementXact">
+ </eg-patron-bill-statement>
+ </ng-container>
+ <ng-container *ngIf="!statementXact">
+ <eg-patron-bills [patronId]="patronId"></eg-patron-bills>
+ </ng-container>
</ng-template>
</li>
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} from './patron.service';
+import {PatronContextService, BillGridEntry} from './patron.service';
import {PatronSearch, PatronSearchComponent
} from '@eg/staff/share/patron/search.component';
patronId: number;
patronTab = 'search';
altTab: string;
+ statementXact: number;
showSummary = true;
loading = true;
this.route.paramMap.subscribe((params: ParamMap) => {
this.patronTab = params.get('tab') || 'search';
this.patronId = +params.get('id');
+ this.statementXact = +params.get('xactId');
if (MAIN_TABS.includes(this.patronTab)) {
this.altTab = null;
import {BarcodesModule} from '@eg/staff/share/barcodes/barcodes.module';
import {ItemsComponent} from './items.component';
import {BillsComponent} from './bills.component';
+import {BillStatementComponent} from './bill-statement.component';
@NgModule({
declarations: [
EditToolbarComponent,
BcSearchComponent,
ItemsComponent,
- BillsComponent
+ BillsComponent,
+ BillStatementComponent
],
imports: [
StaffCommonModule,
import {PatronService} from '@eg/staff/share/patron/patron.service';
import {PatronSearch} from '@eg/staff/share/patron/search.component';
import {StoreService} from '@eg/core/store.service';
+import {CircService, CircDisplayInfo} from '@eg/staff/share/circ/circ.service';
+
+export interface BillGridEntry extends CircDisplayInfo {
+ xact: IdlObject // mbt
+ billingLocation?: string;
+ paymentPending?: number;
+}
export interface CircGridEntry {
title?: string;
private net: NetService,
private org: OrgService,
private auth: AuthService,
+ private circ: CircService,
public patronService: PatronService
) {}
const org = this.org.get(orgId);
return org ? org.shortname() : '';
}
+
+ formatXactForDisplay(xact: IdlObject): BillGridEntry {
+
+ const entry: BillGridEntry = {
+ xact: xact,
+ paymentPending: 0
+ };
+
+ if (xact.summary().xact_type() !== 'circulation') {
+
+ entry.xact.grocery().billing_location(
+ this.org.get(entry.xact.grocery().billing_location()));
+
+ entry.title = xact.summary().last_billing_type();
+ entry.billingLocation =
+ xact.grocery().billing_location().shortname();
+ return entry;
+ }
+
+ entry.xact.circulation().circ_lib(
+ this.org.get(entry.xact.circulation().circ_lib()));
+
+ const circDisplay: CircDisplayInfo =
+ this.circ.getDisplayInfo(xact.circulation());
+
+ entry.billingLocation =
+ xact.circulation().circ_lib().shortname();
+
+ return Object.assign(entry, circDisplay);
+ }
}
path: ':id',
redirectTo: ':id/checkout'
}, {
+ path: ':id/:tab/:xactId/statement',
+ component: PatronComponent,
+ resolve: {resolver : PatronResolver}
+ }, {
path: ':id/:tab',
component: PatronComponent,
resolve: {resolver : PatronResolver}
font-weight: bold;
}
-.common-form.striped-even .row:nth-child(even) {
+.common-form.striped-even .row:nth-child(even),
+ .striped-rows-even .row:nth-child(even) {
+
background-color: rgba(0,0,0,.03);
border-top: 1px solid rgba(0,0,0,.125);
border-bottom: 1px solid rgba(0,0,0,.125);
}
-.common-form.striped-odd .row:nth-child(odd) {
+
+.common-form.striped-odd .row:nth-child(odd),
+ .striped-rows-odd .row:nth-child(odd) {
+
background-color: rgba(0,0,0,.03);
border-top: 1px solid rgba(0,0,0,.125);
border-bottom: 1px solid rgba(0,0,0,.125);