LP1904036 void all billings action
authorBill Erickson <berickxx@gmail.com>
Thu, 11 Mar 2021 22:54:28 +0000 (17:54 -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/bills.component.html
Open-ILS/src/eg2/src/app/staff/circ/patron/bills.component.ts

index bfd4bc3..3a3be34 100644 (file)
@@ -5,11 +5,17 @@
 <eg-alert-dialog #maxPayDialog
   i18n-dialogBody dialogBody="Payments over 100,000 are denied by policy.">
 </eg-alert-dialog>
+
 <eg-confirm-dialog #warnPayDialog
   i18n-dialogBody i18n-dialogTitle dialogTitle="Confirm Payment Amount"
   dialogBody="Are you sure you want to apply a payment of {{paymentAmount | currency}}?">
 </eg-confirm-dialog>
 
+<eg-confirm-dialog #voidBillsDialog
+  i18n-dialogBody i18n-dialogTitle dialogTitle="Void Billings"
+  dialogBody="Are you sure you would like to void {{voidAmount | currency}} in bills for the selected transactions?">
+</eg-confirm-dialog>
+
 <eg-credit-card-dialog [patron]="patron()" #creditCardDialog>
 </eg-credit-card-dialog>
 
   <eg-grid-toolbar-button i18n-label label="Add Billing"
     (onClick)="addBilling()"></eg-grid-toolbar-button>
 
+  <!-- ACTIONS FOR SELECTED -->
+
   <eg-grid-toolbar-button i18n-label label="Select All Refunds"
     (onClick)="selectRefunds()"></eg-grid-toolbar-button>
 
     i18n-label label="Print Bills" (onClick)="printBills($event)">
   </eg-grid-toolbar-action>
 
+  <eg-grid-toolbar-action
+    i18n-label label="Void All Billings" (onClick)="voidBillings($event)">
+  </eg-grid-toolbar-action>
+
+  <!-- COLUMNS -->
+
   <eg-grid-column path="xact.id" [index]="true" label="Bill #" i18n-label>
   </eg-grid-column>
 
index e1a4864..0d0a9fe 100644 (file)
@@ -4,6 +4,7 @@ import {from, empty} 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';
@@ -55,6 +56,7 @@ export class BillsComponent implements OnInit, AfterViewInit {
 
     maxPayAmount = 100000;
     warnPayAmount = 1000;
+    voidAmount = 0;
 
     gridDataSource: GridDataSource = new GridDataSource();
     cellTextGenerator: GridCellTextGenerator;
@@ -63,6 +65,7 @@ export class BillsComponent implements OnInit, AfterViewInit {
     @ViewChild('annotateDialog') private annotateDialog: PromptDialogComponent;
     @ViewChild('maxPayDialog') private maxPayDialog: AlertDialogComponent;
     @ViewChild('warnPayDialog') private warnPayDialog: ConfirmDialogComponent;
+    @ViewChild('voidBillsDialog') private voidBillsDialog: ConfirmDialogComponent;
     @ViewChild('creditCardDialog') private creditCardDialog: CreditCardDialogComponent;
     @ViewChild('billingDialog') private billingDialog: AddBillingDialogComponent;
 
@@ -70,6 +73,7 @@ export class BillsComponent implements OnInit, AfterViewInit {
         private router: Router,
         private route: ActivatedRoute,
         private org: OrgService,
+        private evt: EventService,
         private net: NetService,
         private pcrud: PcrudService,
         private auth: AuthService,
@@ -149,10 +153,12 @@ export class BillsComponent implements OnInit, AfterViewInit {
     // summary, and slot them back into the entries array.
     load(refreshXacts?: number[]): Promise<any> {
 
-        if (!refreshXacts) { this.entries = []; }
+        const entriesFetched: number[] = [];
         this.summary = null;
         this.gridDataSource.requestingData = true;
 
+        if (!refreshXacts) { this.entries = []; }
+
         return this.net.request(
             'open-ils.actor',
             'open-ils.actor.user.transactions.for_billing',
@@ -165,28 +171,51 @@ export class BillsComponent implements OnInit, AfterViewInit {
                 return;
             }
 
-            if (refreshXacts) {
-                let idx = 0;
-                for (;idx < this.entries.length; idx++) {
-                    const entry = this.entries[idx];
-                    if (entry.xact.id() === resp.id()) { break; }
-                }
+            if (!refreshXacts) {
+                this.entries.push(this.formatForDisplay(resp));
+                return;
+            }
 
-                if (idx < this.entries.length) {
-                    // Update the existing entry
-                    this.entries[idx] = this.formatForDisplay(resp);
-                } else {
-                    // Adding a new transaction (e.g. from new billing)
-                    this.entries.push(this.formatForDisplay(resp));
-                }
+            entriesFetched.push(resp.id());
+
+            let idx;
+            for (idx = 0; idx < this.entries.length; idx++) {
+                const entry = this.entries[idx];
+                if (entry.xact.id() === resp.id()) { break; }
+            }
 
+            if (idx < this.entries.length) {
+                // Update the existing entry
+                this.entries[idx] = this.formatForDisplay(resp);
             } else {
+                // Adding a new transaction (e.g. from new billing)
                 this.entries.push(this.formatForDisplay(resp));
             }
+
         })).toPromise()
 
         .then(_ => {
+            if (!refreshXacts) { return; }
+
+            // Refreshing means some transactions may be removed from the list
+            // Remove them from the local entries array.
+            refreshXacts.forEach(xactId => {
+                if (entriesFetched.includes(xactId)) { return; }
+
+                let idx;
+                for (idx = 0; idx < this.entries.length; idx++) {
+                    const entry = this.entries[idx];
+                    if (entry.xact.id() === xactId) { break; }
+                }
+
+                this.billGrid.context.rowSelector.deselect(xactId + '');
+                this.entries.splice(idx, 1);
+            });
+        })
+
+        .then(_ => {
             this.gridDataSource.requestingData = false;
+            if (refreshXacts) { this.context.refreshPatron(); }
             this.billGrid.reload();
         });
     }
@@ -327,7 +356,6 @@ export class BillsComponent implements OnInit, AfterViewInit {
         })
         .then(paymentIds => this.handlePayReceipt(payments, paymentIds))
         .then(_ => this.load(payments.map(p => p[0]))) // load xact IDs
-        .then(_ => this.context.refreshPatron())
         .catch(msg => console.debug('Payment Canceled:', msg))
         .finally(() => this.applyingPayment = false);
     }
@@ -483,10 +511,58 @@ export class BillsComponent implements OnInit, AfterViewInit {
     addBilling() {
         this.billingDialog.open().subscribe(data => {
             if (data) {
-                this.context.refreshPatron();
                 this.load([data.xactId]);
             }
         });
     }
+
+    voidBillings(rows: BillGridEntry[]) {
+
+        const xactIds = rows.map(r => r.xact.id());
+        const billIds = [];
+        let cents = 0;
+
+        from(xactIds)
+        // Grab the billings
+        .pipe(concatMap(xactId => {
+            return this.pcrud.search('mb', {xact: xactId}, {}, {authoritative: true})
+            .pipe(tap(billing => {
+                if (billing.voided() === 'f') {
+                    cents += billing.amount() * 100
+                    billIds.push(billing.id());
+                }
+            }));
+        }))
+        // Confirm the void action
+        .pipe(concatMap(_ => {
+            this.voidAmount = cents / 100;
+            return this.voidBillsDialog.open();
+        }))
+        // Do the void
+        .pipe(concatMap(confirmed => {
+            if (!confirmed) { return empty(); }
+
+            return this.net.requestWithParamList(
+                'open-ils.circ',
+                'open-ils.circ.money.billing.void',
+                [this.auth.token()].concat(billIds) // positional params
+            );
+        }))
+        // Clean up and reresh data
+        .subscribe(resp => {
+            if (!resp) { return; } // canceled
+
+            const evt = this.evt.parse(resp);
+            if (evt) {
+                console.error(evt);
+                alert(evt);
+                return;
+            }
+
+            this.sessionVoided = (this.sessionVoided * 100 + cents) / 100;
+            this.voidAmount = 0;
+            this.load(xactIds);
+        });
+    }
 }