LP1904036 billing statement
authorBill Erickson <berickxx@gmail.com>
Fri, 12 Mar 2021 21:49:53 +0000 (16:49 -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
Open-ILS/src/eg2/src/app/staff/circ/patron/bill-statement.component.ts
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/perlmods/lib/OpenILS/Application/Circ/Money.pm

index a6e807f..f0e11a0 100644 (file)
@@ -1,56 +1,56 @@
 
 <h3 i18n>Transaction: #{{xactId}}</h3>
 
-<div *ngIf="entry" class="striped-rows-odd">
+<div *ngIf="statement" 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">{{context.orgSn(statement.billing_location)}}</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-1">{{statement.summary.billing_total | currency}}</div>
     <div class="col-lg-2" i18n>Title</div>
-    <div class="col-lg-3">{{entry.title}}</div>
+    <div class="col-lg-3">{{statement.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 class="col-lg-2">
+      <span *ngIf="statement.xact.circulation()" i18n>Circulation</span>
+      <span *ngIf="!statement.xact.circulation()" 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-1">{{statement.payment_total | 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 *ngIf="statement.xact.circulation()">
+        {{statement.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">{{statement.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-1">{{statement.balance_due | 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 *ngIf="statement.xact.circulation()">
+        {{statement.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">{{statement.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">
+      <ng-container *ngIf="statement.xact.circulation()">
+        <eg-bool [value]="statement.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 *ngIf="statement.xact.circulation()">
+        {{statement.xact.circulation().checkin_time() | date:'short'}}
       </ng-container>
     </div>
   </div>
 
 <hr class="p-2 m-2"/>
 
-<ul ngbNav #statementNav="ngbNav" class="nav-tabs"
-  [activeId]="statementTab" (navChange)="beforeTabChange($event)">
+<ul ngbNav #statementNav="ngbNav" class="nav-tabs" [activeId]="statementTab">
   <li ngbNavItem="statement">
     <a ngbNavLink i18n>Statement</a>
     <ng-template ngbNavContent>
+      <h4 i18n>Billing Statement</h4>
+      <div class="card tight-card">
+        <div class="card-header rounded">
+          <div class="row">
+            <div class="col-lg-2" i18n>Type</div>
+            <div class="col-lg-4" i18n>Description</div>
+            <div class="col-lg-2" i18n>Amount</div>
+            <div class="col-lg-2" i18n>Balance</div>
+          </div>
+        </div>
+        <div class="card-body">
+                                       <div class="striped-rows-even">
+                                               <div class="row mb-1" *ngFor="let line of statement.lines">
+                                                       <div class="col-lg-2" [ngSwitch]="line.type">
+                                                               <span *ngSwitchCase="'billing'" i18n>Billing</span>
+                                                               <span *ngSwitchCase="'payment'" i18n>Payment</span>
+                                                               <span *ngSwitchCase="'account_adjustment'" i18n>Adjustment</span>
+                                                               <span *ngSwitchCase="'void'" i18n>Void</span>
+                                                       </div>
+                                                       <div class="col-lg-4">
+                                                               <div *ngIf="line.billing_type" class="font-weight-bold">
+                                                                       {{line.billing_type}}
+                                                               </div>
+                                                               <div *ngIf="line.note.length" 
+                                                                       [ngClass]="{'font-weight-bold' : !line.billing_type}">
+                                                                               {{line.note.join(', ')}}
+                                                               </div>
+                                                               <div>
+                                                                       <span>{{line.start_date  | date:'short'}}</span>
+                                                                       <span *ngIf="line.end_date"> - {{line.end_date  | date:'short'}}</span>
+                                                               </div>
+                                                       </div>
+                                                       <div class="col-lg-2" [ngClass]="{'text-danger': line.type != 'billing'}">
+                                                               <span *ngIf="line.type != 'billing'">-</span>{{line.amount | currency}}
+                                                       </div>
+                                                       <div class="col-lg-2">{{line.running_balance | currency}}</div>
+                                               </div>
+                                       </div>
+
+                                       <hr class="m-2 p-2"/>
+                                       
+                                       <div class="row">
+                                               <div class="col-lg-2 offset-lg-8" i18n>Total Charges</div>
+                                               <div class="col-lg-2">{{statement.summary.billing_total | currency}}</div>
+                                       </div>
+                                       <div class="row">
+                                               <div class="col-lg-2 offset-lg-8" i18n>Total Payments</div>
+                                               <div class="col-lg-2">{{statement.summary.payment_total | currency}}</div>
+                                       </div>
+                                       <div class="row" *ngIf="statement.summary.account_adjustment_total > 0">
+                                               <div class="col-lg-2 offset-lg-8" i18n>Total Adjustements</div>
+                                               <div class="col-lg-2">{{statement.summary.account_adjustment_total | currency}}</div>
+                                       </div>
+                                       <div class="row" *ngIf="statement.summary.void_total > 0">
+                                               <div class="col-lg-2 offset-lg-8" i18n>Total Voids</div>
+                                               <div class="col-lg-2">{{statement.summary.void_total | currency}}</div>
+                                       </div>
+
+          <hr class="m-2 p-2"/>
+
+          <div class="row font-weight-bold">
+            <div class="col-lg-2 offset-lg-8" i18n>Balance Due</div>
+            <div class="col-lg-2">{{statement.summary.balance_due | currency}}</div>
+          </div>
+
+        </div>
+      </div>
     </ng-template>
   </li>
   <li ngbNavItem="details">
   </li>
 </ul>
 
-<div [ngbNavOutlet]="statementNav"></div>
+<ng-container *ngIf="statement">
+  <div [ngbNavOutlet]="statementNav"></div>
+</ng-container>
 
-<!--
-Billing LocationBR1Total Billed$4.56Title
-TypegroceryTotal Paid/Credited$2.50Checked Out
-Start3/11/2021 1:34 PMBalance Due$2.06Due Date
-FinishRenewal?Checked In
--->
index d5e2548..ba4b9ef 100644 (file)
@@ -35,8 +35,7 @@ export class BillStatementComponent implements OnInit {
 
     @Input() patronId: number;
     @Input() xactId: number;
-    entry: BillGridEntry;
-    summary: IdlObject;
+    statement: any;
     statementTab = 'statement';
     gridDataSource: GridDataSource = new GridDataSource();
     cellTextGenerator: GridCellTextGenerator;
@@ -70,26 +69,11 @@ export class BillStatementComponent implements OnInit {
             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);
-            }
-        });
+        this.net.request(
+            'open-ils.circ',
+            'open-ils.circ.money.statement.retrieve',
+            this.auth.token(), this.xactId
+        ).subscribe(s => this.statement = s);
     }
 }
 
index 64fca63..531cc4b 100644 (file)
@@ -37,7 +37,7 @@
 <eg-credit-card-dialog [patron]="patron()" #creditCardDialog>
 </eg-credit-card-dialog>
 
-<eg-add-billing-dialog [patronId]="patronId" [newXact]="true" #billingDialog>
+<eg-add-billing-dialog [patronId]="patronId" #billingDialog>
 </eg-add-billing-dialog>
 
 <!-- SUMMARY  -->
     i18n-label label="Adjust To Zero" (onClick)="adjustToZero($event)">
   </eg-grid-toolbar-action>
 
+  <eg-grid-toolbar-action
+    i18n-label label="Add Billing" (onClick)="addBillingForXact($event)">
+  </eg-grid-toolbar-action>
+
   <eg-grid-toolbar-action i18n-label label="Refund" (onClick)="refund($event)">
   </eg-grid-toolbar-action>
 
index bf0a09b..ad6e7d9 100644 (file)
@@ -504,6 +504,7 @@ export class BillsComponent implements OnInit, AfterViewInit {
     }
 
     addBilling() {
+        this.billingDialog.newXact = true;
         this.billingDialog.open().subscribe(data => {
             if (data) {
                 this.load([data.xactId]);
@@ -511,6 +512,30 @@ export class BillsComponent implements OnInit, AfterViewInit {
         });
     }
 
+    addBillingForXact(rows: BillGridEntry[]) {
+        if (rows.length === 0) { return; }
+        const xactIds = rows.map(r => r.xact.id());
+
+        this.billingDialog.newXact = false;
+        const xactsChanged = [];
+
+        from(xactIds)
+        .pipe(concatMap(id => {
+            this.billingDialog.xactId = id;
+            return this.billingDialog.open();
+        }))
+        .pipe(tap(data => {
+            if (data) {
+                xactsChanged.push(data.xactId);
+            }
+        }))
+        .subscribe(null, null, () => {
+            if (xactsChanged.length > 0) {
+                this.load(xactsChanged);
+            }
+        });
+    }
+
     voidBillings(rows: BillGridEntry[]) {
         if (rows.length === 0) { return; }
 
index ac4318a..e3d477f 100644 (file)
@@ -76,7 +76,7 @@
         </li>
 
         <li ngbNavItem="bills" [disabled]="!context.patron">
-          <a ngbNavLink i18n>
+          <a ngbNavLink (click)="billsTabClicked()" i18n>
             Bills 
             <span [ngClass]="{'text-danger': counts('fines', 'balance_owed') > 0}">
               ({{counts('fines', 'balance_owed') | currency}})
index 0e755f3..d155585 100644 (file)
@@ -100,6 +100,13 @@ export class PatronComponent implements OnInit, AfterViewInit {
         this.routeToTab();
     }
 
+    // The bills tab has various sub-interfaces.  If the user is already
+    // on the Bills tab and clicks the tab, return them to the main bills
+    // screen.
+    billsTabClicked() {
+        this.router.navigate(['/staff/circ/patron', this.patronId, 'bills']);
+    }
+
     routeToTab() {
         let url = '/staff/circ/patron/';
 
index 4163cf0..975479f 100644 (file)
@@ -23,6 +23,7 @@ my $U = "OpenILS::Application::AppUtils";
 my $CC = "OpenILS::Application::Circ::CircCommon";
 
 use OpenSRF::EX qw(:try);
+use OpenSRF::Utils::JSON;
 use OpenILS::Perm;
 use Data::Dumper;
 use OpenILS::Event;
@@ -1361,8 +1362,41 @@ sub retrieve_statement {
         $line->{note} = $line->{note} ? [split(/\n/, $line->{note})] : [];
     }
 
+    my $xact = $e->retrieve_money_billable_transaction([
+        $xact_id, {
+            flesh => 5,
+            flesh_fields => {
+                mbt => [qw/circulation grocery/],
+                circ => [qw/target_copy/],
+                acp =>  [qw/call_number/],
+                acn =>  [qw/record/],
+                bre =>  [qw/wide_display_entry/]
+            },
+            select => {bre => ['id']} 
+        }
+    ]);
+
+    my $title;
+    my $billing_location;
+    if ($xact->circulation) {
+        $billing_location = $xact->circulation->circ_lib;
+        my $copy = $xact->circulation->target_copy;
+        if ($copy->call_number->id == -1) {
+            $title = $copy->dummy_title;
+        } else {
+            $title = OpenSRF::Utils::JSON->JSON2perl(
+                $copy->call_number->record->wide_display_entry->title);
+        }
+    } else {
+        $billing_location = $xact->grocery->billing_location;
+        $title = $xact->grocery->note;
+    }
+
     return {
         xact_id => $xact_id,
+        xact => $xact,
+        title => $title,
+        billing_location => $billing_location,
         summary => {
             balance_due => $totals{billing} - ($totals{payment} + $totals{account_adjustment} + $totals{void}),
             billing_total => $totals{billing},