LP1904036 billing statement
authorBill Erickson <berickxx@gmail.com>
Fri, 12 Mar 2021 18:32:38 +0000 (13:32 -0500)
committerGalen Charlton <gmc@equinoxOLI.org>
Fri, 28 Oct 2022 00:13:26 +0000 (20:13 -0400)
Signed-off-by: Bill Erickson <berickxx@gmail.com>
Signed-off-by: Jane Sandberg <js7389@princeton.edu>
Signed-off-by: Galen Charlton <gmc@equinoxOLI.org>
Open-ILS/src/eg2/src/app/staff/circ/patron/bill-statement.component.html [new file with mode: 0644]
Open-ILS/src/eg2/src/app/staff/circ/patron/bill-statement.component.ts [new file with mode: 0644]
Open-ILS/src/eg2/src/app/staff/circ/patron/bills.component.html
Open-ILS/src/eg2/src/app/staff/circ/patron/bills.component.ts
Open-ILS/src/eg2/src/app/staff/circ/patron/patron.component.html
Open-ILS/src/eg2/src/app/staff/circ/patron/patron.component.ts
Open-ILS/src/eg2/src/app/staff/circ/patron/patron.module.ts
Open-ILS/src/eg2/src/app/staff/circ/patron/patron.service.ts
Open-ILS/src/eg2/src/app/staff/circ/patron/routing.module.ts
Open-ILS/src/eg2/src/styles.css

diff --git a/Open-ILS/src/eg2/src/app/staff/circ/patron/bill-statement.component.html b/Open-ILS/src/eg2/src/app/staff/circ/patron/bill-statement.component.html
new file mode 100644 (file)
index 0000000..a6e807f
--- /dev/null
@@ -0,0 +1,82 @@
+
+<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
+-->
diff --git a/Open-ILS/src/eg2/src/app/staff/circ/patron/bill-statement.component.ts b/Open-ILS/src/eg2/src/app/staff/circ/patron/bill-statement.component.ts
new file mode 100644 (file)
index 0000000..d5e2548
--- /dev/null
@@ -0,0 +1,95 @@
+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);
+            }
+        });
+    }
+}
+
index c1dd868..64fca63 100644 (file)
 </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"
index dede530..bf0a09b 100644 (file)
@@ -11,7 +11,7 @@ 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} 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';
@@ -27,12 +27,6 @@ import {AddBillingDialogComponent} from '@eg/staff/share/billing/billing-dialog.
 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',
@@ -183,7 +177,7 @@ export class BillsComponent implements OnInit, AfterViewInit {
             }
 
             if (!refreshXacts) {
-                this.entries.push(this.formatForDisplay(resp));
+                this.entries.push(this.context.formatXactForDisplay(resp));
                 return;
             }
 
@@ -197,10 +191,10 @@ export class BillsComponent implements OnInit, AfterViewInit {
 
             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()
@@ -231,36 +225,6 @@ export class BillsComponent implements OnInit, AfterViewInit {
         });
     }
 
-    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;
     }
@@ -634,5 +598,10 @@ export class BillsComponent implements OnInit, AfterViewInit {
             this.paymentAmount = null;
         });
     }
+
+    showStatement(row: BillGridEntry) {
+        this.router.navigate(['/staff/circ/patron',
+            this.patronId, 'bills', row.xact.id(), 'statement']);
+    }
 }
 
index e92bdfd..ac4318a 100644 (file)
             </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>
 
index 6c5eeba..0e755f3 100644 (file)
@@ -5,7 +5,7 @@ import {NetService} from '@eg/core/net.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} from './patron.service';
+import {PatronContextService, BillGridEntry} from './patron.service';
 import {PatronSearch, PatronSearchComponent
     } from '@eg/staff/share/patron/search.component';
 
@@ -21,6 +21,7 @@ export class PatronComponent implements OnInit, AfterViewInit {
     patronId: number;
     patronTab = 'search';
     altTab: string;
+    statementXact: number;
     showSummary = true;
     loading = true;
 
@@ -58,6 +59,7 @@ export class PatronComponent implements OnInit, AfterViewInit {
         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;
index 31b179f..89a8473 100644 (file)
@@ -21,6 +21,7 @@ import {BcSearchComponent} from './bcsearch.component';
 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: [
@@ -33,7 +34,8 @@ import {BillsComponent} from './bills.component';
     EditToolbarComponent,
     BcSearchComponent,
     ItemsComponent,
-    BillsComponent
+    BillsComponent,
+    BillStatementComponent
   ],
   imports: [
     StaffCommonModule,
index 8eb24ab..c9665be 100644 (file)
@@ -6,6 +6,13 @@ import {AuthService} from '@eg/core/auth.service';
 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;
@@ -103,6 +110,7 @@ export class PatronContextService {
         private net: NetService,
         private org: OrgService,
         private auth: AuthService,
+        private circ: CircService,
         public patronService: PatronService
     ) {}
 
@@ -220,6 +228,36 @@ export class PatronContextService {
         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);
+    }
 }
 
 
index 06c95d8..5b53fb8 100644 (file)
@@ -26,6 +26,10 @@ const routes: Routes = [{
     path: ':id',
     redirectTo: ':id/checkout'
   }, {
+    path: ':id/:tab/:xactId/statement',
+    component: PatronComponent,
+    resolve: {resolver : PatronResolver}
+  }, {
     path: ':id/:tab',
     component: PatronComponent,
     resolve: {resolver : PatronResolver}
index f75803f..71e7382 100644 (file)
@@ -207,12 +207,17 @@ a {
   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);