LP1904036 checkout in transit dialog
authorBill Erickson <berickxx@gmail.com>
Mon, 12 Apr 2021 19:04:06 +0000 (15:04 -0400)
committerGalen Charlton <gmc@equinoxOLI.org>
Fri, 28 Oct 2022 00:13:30 +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/share/circ/circ.module.ts
Open-ILS/src/eg2/src/app/staff/share/circ/circ.service.ts
Open-ILS/src/eg2/src/app/staff/share/circ/components.component.html
Open-ILS/src/eg2/src/app/staff/share/circ/components.component.ts
Open-ILS/src/eg2/src/app/staff/share/circ/in-transit-dialog.component.html [new file with mode: 0644]
Open-ILS/src/eg2/src/app/staff/share/circ/in-transit-dialog.component.ts [new file with mode: 0644]
Open-ILS/src/eg2/src/app/staff/share/circ/route-dialog.component.ts

index 189d27a..8c8fef8 100644 (file)
@@ -11,6 +11,7 @@ import {CircComponentsComponent} from './components.component';
 import {CircEventsComponent} from './events-dialog.component';
 import {OpenCircDialogComponent} from './open-circ-dialog.component';
 import {RouteDialogComponent} from './route-dialog.component';
+import {CopyInTransitDialogComponent} from './in-transit-dialog.component';
 
 @NgModule({
     declarations: [
@@ -21,6 +22,7 @@ import {RouteDialogComponent} from './route-dialog.component';
         ClaimsReturnedDialogComponent,
         CircEventsComponent,
         RouteDialogComponent,
+        CopyInTransitDialogComponent,
         OpenCircDialogComponent
     ],
     imports: [
index 3c387dc..f0a6a91 100644 (file)
@@ -131,6 +131,7 @@ export interface CheckoutParams {
 
 export interface CircResultCommon {
     index: number;
+    params: CheckinParams | CheckoutParams;
     firstEvent: EgEvent;
     allEvents: EgEvent[];
     success: boolean;
@@ -141,6 +142,7 @@ export interface CircResultCommon {
     parent_circ?: IdlObject;
     hold?: IdlObject;
     patron?: IdlObject;
+    transit?: IdlObject;
 
     // Calculated values
     title?: string;
@@ -170,7 +172,6 @@ export interface CheckinParams {
 
 export interface CheckinResult extends CircResultCommon {
     params: CheckinParams;
-    transit?: IdlObject;
     mbts?: IdlObject;
     routeTo?: string; // org name or in-branch destination
     destOrg?: IdlObject;
@@ -266,8 +267,9 @@ export class CircService {
 
     // find the open transit for the given copy barcode; flesh the org
     // units locally.
-    findCopyTransit(result: CheckinResult): Promise<IdlObject> {
-        // NOTE: evt.payload.transit may exist, but it's not necessarily
+    // Sets result.transit
+    findCopyTransit(result: CircResultCommon): Promise<IdlObject> {
+        // NOTE: result.transit may exist, but it's not necessarily
         // the transit we want, since a transit close + open in the API
         // returns the closed transit.
 
@@ -276,16 +278,56 @@ export class CircService {
                 cancel_time : null,
                 target_copy: result.copy.id()
             }, {
+                limit : 1,
+                order_by : {atc : 'source_send_time desc'},
+            }, {authoritative : true}
+        ).toPromise().then(transit => {
+            if (transit) {
+                transit.source(this.org.get(transit.source()));
+                transit.dest(this.org.get(transit.dest()));
+                result.transit = transit;
+                return transit;
+            }
+
+            return Promise.reject('No transit found');
+        });
+    }
+
+    // Sets result.transit and result.copy
+    findCopyTransitByBarcode(result: CircResultCommon): Promise<IdlObject> {
+        // NOTE: result.transit may exist, but it's not necessarily
+        // the transit we want, since a transit close + open in the API
+        // returns the closed transit.
+
+         const barcode = result.params.copy_barcode;
+
+         return this.pcrud.search('atc', {
+                dest_recv_time : null,
+                cancel_time : null
+            }, {
                 flesh : 1,
                 flesh_fields : {atc : ['target_copy']},
+                join : {
+                    acp : {
+                        filter : {
+                            barcode : barcode,
+                            deleted : 'f'
+                        }
+                    }
+                },
                 limit : 1,
-                order_by : {atc : 'source_send_time desc'},
+                order_by : {atc : 'source_send_time desc'}
             }, {authoritative : true}
+
         ).toPromise().then(transit => {
-            transit.source(this.org.get(transit.source()));
-            transit.dest(this.org.get(transit.dest()));
-            result.routeTo = transit.dest().shortname();
-            return transit;
+            if (transit) {
+                transit.source(this.org.get(transit.source()));
+                transit.dest(this.org.get(transit.dest()));
+                result.transit = transit;
+                result.copy = transit.target_copy();
+                return transit;
+            }
+            return Promise.reject('No transit found');
         });
     }
 
@@ -325,7 +367,8 @@ export class CircService {
         return this.net.request(
             'open-ils.circ', method,
             this.auth.token(), this.apiParams(params)).toPromise()
-        .then(result => this.processCheckoutResult(params, result));
+        .then(result => this.unpackCheckoutData(params, result))
+        .then(result => this.processCheckoutResult(result));
     }
 
     renew(params: CheckoutParams): Promise<CheckoutResult> {
@@ -339,10 +382,12 @@ export class CircService {
         return this.net.request(
             'open-ils.circ', method,
             this.auth.token(), this.apiParams(params)).toPromise()
-        .then(result => this.processCheckoutResult(params, result));
+        .then(result => this.unpackCheckoutData(params, result))
+        .then(result => this.processCheckoutResult(result));
     }
 
-    processCheckoutResult(
+
+    unpackCheckoutData(
         params: CheckoutParams, response: any): Promise<CheckoutResult> {
 
         const allEvents = Array.isArray(response) ?
@@ -354,43 +399,40 @@ export class CircService {
         const firstEvent = allEvents[0];
         const payload = firstEvent.payload;
 
-        if (!payload) {
-            this.audio.play('error.unknown.no_payload');
-            return Promise.reject();
-        }
-
         const result: CheckoutResult = {
             index: CircService.resultIndex++,
             firstEvent: firstEvent,
             allEvents: allEvents,
             params: params,
-            success: false,
-            circ: payload.circ,
-            copy: payload.copy,
-            record: payload.record,
-            nonCatCirc: payload.noncat_circ
+            success: false
         };
 
-        if (result.record) {
-            result.title = result.record.title();
-            result.author = result.record.author();
-            result.isbn = result.record.isbn();
+        // Some scenarios (e.g. copy in transit) have no payload,
+        // which is OK.
+        if (!payload) { return Promise.resolve(result); }
 
-        } else if (result.copy) {
-            result.title = result.copy.dummy_title();
-            result.author = result.copy.dummy_author();
-            result.isbn = result.copy.dummy_isbn();
-        }
+        result.circ = payload.circ,
+        result.copy = payload.copy,
+        result.record = payload.record,
+        result.nonCatCirc = payload.noncat_circ
+
+        return this.fleshCommonData(result);
+    }
+
+    processCheckoutResult(result: CheckoutResult): Promise<CheckoutResult> {
 
-        const overridable = result.params._renewal ?
+        let overridable;
+        try {
+        overridable = result.params._renewal ?
             CAN_OVERRIDE_RENEW_EVENTS : CAN_OVERRIDE_CHECKOUT_EVENTS;
+        } catch (E) { console.error(E) }
 
-        if (allEvents.filter(
+        if (result.allEvents.filter(
             e => overridable.includes(e.textcode)).length > 0) {
-            return this.handleOverridableCheckoutEvents(result, allEvents);
+            return this.handleOverridableCheckoutEvents(result);
         }
 
-        switch (firstEvent.textcode) {
+        switch (result.firstEvent.textcode) {
             case 'SUCCESS':
                 result.success = true;
                 this.audio.play('success.checkout');
@@ -401,12 +443,77 @@ export class CircService {
 
             case 'OPEN_CIRCULATION_EXISTS':
                 return this.handleOpenCirc(result);
+
+            case 'COPY_IN_TRANSIT':
+                this.audio.play('warning.checkout.in_transit');
+                return this.copyInTransitDialog(result);
+
+            /*
+            case 'PATRON_CARD_INACTIVE':
+            case 'PATRON_INACTIVE':
+            case 'PATRON_ACCOUNT_EXPIRED':
+            case 'CIRC_CLAIMS_RETURNED':
+                egCore.audio.play('warning.checkout');
+                return service.exit_alert(
+                    egCore.strings[evt[0].textcode],
+                    {barcode : params.copy_barcode}
+                );
+                */
+
+
+            /*
+            case 'PATRON_CARD_INACTIVE':
+            case 'PATRON_INACTIVE':
+            case 'PATRON_ACCOUNT_EXPIRED':
+            case 'CIRC_CLAIMS_RETURNED':
+                egCore.audio.play('warning.checkout');
+                return this.exit_alert(
+                    egCore.strings[evt[0].textcode],
+                    {barcode : params.copy_barcode}
+                );
+
+            default:
+                egCore.audio.play('error.checkout.unknown');
+                return this.exit_alert(
+                    egCore.strings.CHECKOUT_FAILED_GENERIC, {
+                        barcode : params.copy_barcode,
+                        textcode : evt[0].textcode,
+                        desc : evt[0].desc
+                    }
+                );
+            */
         }
 
         return Promise.resolve(result);
     }
 
 
+    copyInTransitDialog(result: CheckoutResult): Promise<CheckoutResult> {
+        this.components.copyInTransitDialog.checkout = result;
+
+        return this.findCopyTransitByBarcode(result)
+        .then(_ => this.components.copyInTransitDialog.open().toPromise())
+        .then(cancelAndCheckout => {
+            if (cancelAndCheckout) {
+
+                return this.abortTransit(result.transit.id())
+                .then(_ => {
+                    // We had to look up the copy from the barcode since
+                    // it was not embedded in the result event.  Since
+                    // we have the specifics on the copy, go ahead and
+                    // copy them into the params we use for the follow
+                    // up checkout.
+                    result.params.copy_barcode = result.copy.barcode()
+                    result.params.copy_id = result.copy.id()
+                    return this.checkout(result.params);
+                });
+
+            } else {
+                return result;
+            }
+        })
+    }
+
     // Ask the user if we should resolve the circulation and check
     // out to the user or leave it alone.
     // When resolving and checking out, renew if it's for the same
@@ -459,10 +566,10 @@ export class CircService {
         });
     }
 
-    handleOverridableCheckoutEvents(
-        result: CheckoutResult, events: EgEvent[]): Promise<CheckoutResult> {
+    handleOverridableCheckoutEvents(result: CheckoutResult): Promise<CheckoutResult> {
         const params = result.params;
-        const firstEvent = events[0];
+        const firstEvent = result.firstEvent;
+        const events = result.allEvents;
 
         if (params._override) {
             // Should never get here.  Just being safe.
@@ -539,7 +646,7 @@ export class CircService {
         .then(result => this.processCheckinResult(result));
     }
 
-    collectCommonData(result: CircResultCommon): Promise<any> {
+    fleshCommonData(result: CircResultCommon): Promise<CircResultCommon> {
 
         const copy = result.copy;
         const volume = result.volume;
@@ -587,7 +694,7 @@ export class CircService {
             }
         }
 
-        return promise;
+        return promise.then(_ => result);
     }
 
     unpackCheckinData(params: CheckinParams, response: any): Promise<CheckinResult> {
@@ -649,7 +756,7 @@ export class CircService {
             result.mbts = parent_circ.billable_transaction().summary();
         }
 
-        return this.collectCommonData(result).then(_ => result);
+        return this.fleshCommonData(result).then(_ => result);
     }
 
     processCheckinResult(result: CheckinResult): Promise<CheckinResult> {
@@ -858,5 +965,20 @@ export class CircService {
             return from(this.checkin(cparams));
         }));
     }
+
+    abortTransit(transitId: number): Promise<any> {
+        return this.net.request(
+            'open-ils.circ',
+            'open-ils.circ.transit.abort',
+            this.auth.token(), {transitid : transitId}
+        ).toPromise().then(resp => {
+            const evt = this.evt.parse(resp);
+            if (evt) {
+                alert(evt);
+                return Promise.reject(evt.toString());
+            }
+            return Promise.resolve();
+        });
+    }
 }
 
index 2e17a7c..123acda 100644 (file)
@@ -28,6 +28,7 @@
 <eg-open-circ-dialog #openCircDialog></eg-open-circ-dialog>
 
 <eg-circ-route-dialog #routeDialog></eg-circ-route-dialog>
+<eg-copy-in-transit-dialog #copyInTransitDialog></eg-copy-in-transit-dialog>
 
 <eg-string #holdShelfStr i18n-text text="Hold Shelf"></eg-string>
 <eg-string #catalogingStr i18n-text text="Cataloging"></eg-string>
index 0736b3c..921e591 100644 (file)
@@ -6,6 +6,7 @@ import {StringComponent} from '@eg/share/string/string.component';
 import {AlertDialogComponent} from '@eg/share/dialog/alert.component';
 import {OpenCircDialogComponent} from './open-circ-dialog.component';
 import {RouteDialogComponent} from './route-dialog.component';
+import {CopyInTransitDialogComponent} from './in-transit-dialog.component';
 
 /* Container component for sub-components used by circulation actions.
  *
@@ -27,6 +28,7 @@ export class CircComponentsComponent {
     @ViewChild('locationAlertDialog') locationAlertDialog: AlertDialogComponent;
     @ViewChild('uncatAlertDialog') uncatAlertDialog: AlertDialogComponent;
     @ViewChild('routeDialog') routeDialog: RouteDialogComponent;
+    @ViewChild('copyInTransitDialog') copyInTransitDialog: CopyInTransitDialogComponent;
 
     @ViewChild('holdShelfStr') holdShelfStr: StringComponent;
     @ViewChild('catalogingStr') catalogingStr: StringComponent;
diff --git a/Open-ILS/src/eg2/src/app/staff/share/circ/in-transit-dialog.component.html b/Open-ILS/src/eg2/src/app/staff/share/circ/in-transit-dialog.component.html
new file mode 100644 (file)
index 0000000..5af1141
--- /dev/null
@@ -0,0 +1,35 @@
+<ng-template #dialogContent>
+  <div class="modal-header bg-info">
+    <h4 i18n>Item In Transit</h4>
+    <button type="button" class="close"
+      i18n-aria-label aria-label="Close" (click)="close()">
+      <span aria-hidden="true">&times;</span>
+    </button>
+  </div>
+  <div class="modal-body">
+    <strong i18n>There is an open transit on item {{checkout.copy.barcode()}}</strong>
+
+    <div class="row mt-3">
+      <div class="col-lg-4" i18n>Transit Date:</div>
+      <div class="col-lg-4">{{checkout.transit.source_send_time() | date:'short'}}</div>
+    </div>
+    <div class="row mt-2">
+      <div class="col-lg-4" i18n>Transit Source:</div>
+      <div class="col-lg-4">{{checkout.transit.source().shortname()}}</div>
+    </div>
+    <div class="row mt-2">
+      <div class="col-lg-4" i18n>Transit Destination:</div>
+      <div class="col-lg-4">{{checkout.transit.dest().shortname()}}</div>
+    </div>
+  </div>
+  <div class="modal-footer">
+    <button type="button" class="btn btn-success" (click)="close(true)" i18n>
+      Cancel Transit then Checkout
+    </button>
+    <button type="button" class="btn btn-warning" (click)="close()" i18n>
+      Cancel
+    </button>
+  </div>
+</ng-template>
+
+
diff --git a/Open-ILS/src/eg2/src/app/staff/share/circ/in-transit-dialog.component.ts b/Open-ILS/src/eg2/src/app/staff/share/circ/in-transit-dialog.component.ts
new file mode 100644 (file)
index 0000000..60098ea
--- /dev/null
@@ -0,0 +1,31 @@
+import {Component, OnInit, Output, Input, ViewChild, EventEmitter} from '@angular/core';
+import {empty, of, from, Observable} from 'rxjs';
+import {concatMap} from 'rxjs/operators';
+import {IdlService, IdlObject} from '@eg/core/idl.service';
+import {PcrudService} from '@eg/core/pcrud.service';
+import {OrgService} from '@eg/core/org.service';
+import {StringComponent} from '@eg/share/string/string.component';
+import {AlertDialogComponent} from '@eg/share/dialog/alert.component';
+import {NgbModal, NgbModalOptions} from '@ng-bootstrap/ng-bootstrap';
+import {DialogComponent} from '@eg/share/dialog/dialog.component';
+import {CircService, CheckinResult} from './circ.service';
+import {ServerStoreService} from '@eg/core/server-store.service';
+import {AudioService} from '@eg/share/util/audio.service';
+import {PrintService} from '@eg/share/print/print.service';
+
+/** Route Item Dialog */
+
+@Component({
+  templateUrl: 'in-transit-dialog.component.html',
+  selector: 'eg-copy-in-transit-dialog'
+})
+export class CopyInTransitDialogComponent extends DialogComponent {
+
+    checkout: CheckinResult;
+
+    constructor(private modal: NgbModal) {
+        super(modal);
+    }
+}
+
+
index 9328df3..aa90d3f 100644 (file)
@@ -74,7 +74,8 @@ export class RouteDialogComponent extends DialogComponent {
             promise = promise.then(_ => this.circ.findCopyTransit(this.checkin))
             .then(transit => {
                 this.checkin.transit = transit;
-                this.checkin.destOrg = this.org.get(transit.dest());
+                this.checkin.destOrg = transit.dest();
+                this.checkin.routeTo = transit.dest().shortname();
                 return this.circ.getOrgAddr(this.checkin.destOrg.id(), 'holds_address');
             })
             .then(addr => {