LP1904036 Billing continued
authorBill Erickson <berickxx@gmail.com>
Mon, 8 Mar 2021 15:47:05 +0000 (10:47 -0500)
committerGalen Charlton <gmc@equinoxOLI.org>
Fri, 28 Oct 2022 00:13:25 +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
Open-ILS/src/eg2/src/app/staff/circ/patron/resolver.service.ts
Open-ILS/src/perlmods/lib/OpenILS/Application/Actor.pm

index a211e96..5352312 100644 (file)
       <label class="form-check-label" for="annotate" i18n>Annotate</label>
     </div>
     <div class="ml-2">
-      <button class="btn btn-outline-dark" (click)="applyPayment()" i18n>Apply Payment</button>
+      <button class="btn btn-outline-dark" (click)="applyPayment()" 
+        [disabled]="disablePayment()" i18n>Apply Payment</button>
     </div>
   </div>
 </div>
index ba8d39c..9feb702 100644 (file)
@@ -1,11 +1,11 @@
 import {Component, Input, OnInit, AfterViewInit, ViewChild} from '@angular/core';
 import {Router, ActivatedRoute, ParamMap} from '@angular/router';
 import {from, empty} from 'rxjs';
-import {concatMap, tap} from 'rxjs/operators';
+import {concatMap, tap, takeLast} from 'rxjs/operators';
 import {NgbNav, NgbNavChangeEvent} from '@ng-bootstrap/ng-bootstrap';
 import {IdlObject} from '@eg/core/idl.service';
 import {NetService} from '@eg/core/net.service';
-import {PcrudService} from '@eg/core/pcrud.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';
@@ -53,6 +53,7 @@ export class BillsComponent implements OnInit, AfterViewInit {
     sessionVoided = 0;
     paymentType = 'cash_payment';
     checkNumber: string;
+    payAmount: number;
     annotatePayment = false;
     entries: BillGridEntry[];
 
@@ -103,30 +104,27 @@ export class BillsComponent implements OnInit, AfterViewInit {
 
     load() {
 
-        const xactIds = [];
-
-
-        // TODO: run this in a single pcrud transaction
-
-        this.pcrud.retrieve('mous', this.patronId, {}, {authoritative : true})
-        .pipe(tap(sum => this.summary = sum))
-        .pipe(concatMap(_ => {
-            return this.pcrud.search('mbts',
-                {usr: this.patronId, balance_owed: {'<>' : 0}},
-                {select: {mbts: ['id']}}, {authoritative : true}
-            ).pipe(tap(summary => xactIds.push(summary.id())));
-        }))
-        .pipe(concatMap(_ => {
-            this.entries = [];
-            return this.pcrud.search('mbt', {id: xactIds}, {
-                flesh: XACT_FLESH_DEPTH,
-                flesh_fields: XACT_FLESH_FIELDS,
-                order_by: {mbts : ['xact_start']},
-                select: {bre : ['id']}
-                }, {authoritative : true}
-            ).pipe(tap(xact => this.entries.push(this.formatForDisplay(xact))));
-        }))
-        .subscribe(null, null, () => this.billGrid.reload());
+        this.summary = null;
+        this.entries = [];
+        this.gridDataSource.requestingData = true;
+
+        this.net.request('open-ils.actor',
+            'open-ils.actor.user.transactions.for_billing',
+            this.auth.token(), this.patronId
+        ).subscribe(
+            resp => {
+                if (!this.summary) { // 1st response is summary
+                    this.summary = resp;
+                } else {
+                    this.entries.push(this.formatForDisplay(resp));
+                }
+            },
+            null,
+            () => {
+                this.gridDataSource.requestingData = false;
+                this.billGrid.reload();
+            }
+        );
     }
 
     formatForDisplay(xact: IdlObject): BillGridEntry {
@@ -156,6 +154,16 @@ export class BillsComponent implements OnInit, AfterViewInit {
         return this.context.patron;
     }
 
+    disablePayment(): boolean {
+        if (!this.billGrid) { return true; } // still loading
+
+        // TODO: pay amount can be zero when refunding
+        return (
+            this.payAmount <= 0 ||
+            this.billGrid.context.rowSelector.selected().length === 0
+        );
+    }
+
     // TODO
     refundsAvailable(): number {
         return 0;
index e9ea79d..bb71414 100644 (file)
@@ -24,8 +24,8 @@ export class PatronResolver implements Resolve<Promise<any[]>> {
 
     fetchSettings(): Promise<any> {
 
-        // Some of these are used by the shared circ service.
-        // Go ahead and precache them since we're making the call anyway.
+        // Some of these are used by the shared circ services.
+        // Precache them since we're making the call anyway.
         return this.store.getItemBatch([
           'eg.circ.patron.summary.collapse',
           'circ.do_not_tally_claims_returned',
@@ -35,7 +35,13 @@ export class PatronResolver implements Resolve<Promise<any[]>> {
           'ui.admin.patron_log.max_entries',
           'circ.staff_client.do_not_auto_attempt_print',
           'circ.clear_hold_on_checkout',
-          'ui.circ.suppress_checkin_popups'
+          'ui.circ.suppress_checkin_popups',
+          'ui.circ.billing.uncheck_bills_and_unfocus_payment_box',
+          'ui.circ.billing.amount_warn',
+          'ui.circ.billing.amount_limit',
+          'circ.staff_client.do_not_auto_attempt_print',
+          'circ.disable_patron_credit',
+          'credit.processor.default'
         ]).then(settings => {
             this.context.noTallyClaimsReturned =
                 settings['circ.do_not_tally_claims_returned'];
index 01741b5..f6b97d0 100644 (file)
@@ -5343,4 +5343,87 @@ sub filter_group_entry_crud {
     }
 }
 
+
+__PACKAGE__->register_method(
+    method        => 'user_billing_xacts',
+    api_name      => 'open-ils.actor.user.transactions.for_billing',
+    signature     => {
+        desc   => q/Returns a stream of user billing data appropriate for
+            display in the user bills UI.  API is natively "authoritative"./,
+        params => [
+            {desc => 'Authentication token', type => 'string'},
+            {desc => 'User ID', type => 'number'}
+        ],
+        return => {
+            desc => q/First response is the user money summary, following
+                responses are fleshed billable transactions/
+        }
+    }
+);
+
+sub user_billing_xacts {
+    my ($self, $client, $auth, $user_id) = @_;
+
+    my $e = new_editor(authtoken => $auth, xact => 1);
+    return $e->die_event unless $e->checkauth;
+
+    my $user = $e->retrieve_actor_user($user_id) or return $e->die_event;
+
+    return $e->die_event unless 
+        $e->allowed('VIEW_USER_TRANSACTIONS', $user->home_ou);
+
+    # Start with the user summary.
+    $client->respond($e->retrieve_money_user_summary($user_id));
+
+    my $xact_ids = $e->json_query({
+        select => {mbts => ['id']},
+        from => 'mbts',
+        where => {
+            usr => $user_id,
+            balance_owed => {'<>' => 0}
+        },
+        order_by => {mbts => {xact_start => 'asc'}}
+    });
+
+    for my $xact_id (map { $_->{id} } @$xact_ids) {
+
+        my $xact = $e->retrieve_money_billable_transaction([
+            $xact_id, {
+                flesh => 5,
+                flesh_fields => {
+                                   mbt => [qw/summary circulation grocery/],
+                                   circ => [qw/
+                        target_copy 
+                        workstation 
+                        checkin_workstation 
+                        circ_lib
+                    /],
+                                   acp =>  [qw/
+                                           call_number
+                                           holds_count
+                                           status
+                                           circ_lib
+                                           location
+                                           floating
+                                           age_protect
+                                           parts
+                                   /],
+                                   acpm => [qw/part/],
+                                   acn =>  [qw/record owning_lib prefix suffix/],
+                                   bre =>  [qw/wide_display_entry/]
+                },
+                # Avoid adding the MARXML
+                # Fleshed fields are implicitly included.
+                select => {bre => ['id']} 
+            }
+        ]);
+
+        $client->respond($xact);
+    }
+
+    $e->rollback;
+
+    return undef;
+}
+
 1;