From fdf6b4331dcf06926f6c1a784ef83e8b048a5473 Mon Sep 17 00:00:00 2001 From: Bill Erickson Date: Mon, 12 Apr 2021 15:04:06 -0400 Subject: [PATCH] LP1904036 checkout in transit dialog Signed-off-by: Bill Erickson Signed-off-by: Jane Sandberg Signed-off-by: Galen Charlton --- .../eg2/src/app/staff/share/circ/circ.module.ts | 2 + .../eg2/src/app/staff/share/circ/circ.service.ts | 202 +++++++++++++++++---- .../app/staff/share/circ/components.component.html | 1 + .../app/staff/share/circ/components.component.ts | 2 + .../share/circ/in-transit-dialog.component.html | 35 ++++ .../share/circ/in-transit-dialog.component.ts | 31 ++++ .../app/staff/share/circ/route-dialog.component.ts | 3 +- 7 files changed, 235 insertions(+), 41 deletions(-) create mode 100644 Open-ILS/src/eg2/src/app/staff/share/circ/in-transit-dialog.component.html create mode 100644 Open-ILS/src/eg2/src/app/staff/share/circ/in-transit-dialog.component.ts diff --git a/Open-ILS/src/eg2/src/app/staff/share/circ/circ.module.ts b/Open-ILS/src/eg2/src/app/staff/share/circ/circ.module.ts index 189d27a138..8c8fef8549 100644 --- a/Open-ILS/src/eg2/src/app/staff/share/circ/circ.module.ts +++ b/Open-ILS/src/eg2/src/app/staff/share/circ/circ.module.ts @@ -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: [ diff --git a/Open-ILS/src/eg2/src/app/staff/share/circ/circ.service.ts b/Open-ILS/src/eg2/src/app/staff/share/circ/circ.service.ts index 3c387dcc56..f0a6a9189d 100644 --- a/Open-ILS/src/eg2/src/app/staff/share/circ/circ.service.ts +++ b/Open-ILS/src/eg2/src/app/staff/share/circ/circ.service.ts @@ -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 { - // NOTE: evt.payload.transit may exist, but it's not necessarily + // Sets result.transit + findCopyTransit(result: CircResultCommon): Promise { + // 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 { + // 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 { @@ -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 { 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 { - 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 { + 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 { + handleOverridableCheckoutEvents(result: CheckoutResult): Promise { 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 { + fleshCommonData(result: CircResultCommon): Promise { 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 { @@ -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 { @@ -858,5 +965,20 @@ export class CircService { return from(this.checkin(cparams)); })); } + + abortTransit(transitId: number): Promise { + 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(); + }); + } } diff --git a/Open-ILS/src/eg2/src/app/staff/share/circ/components.component.html b/Open-ILS/src/eg2/src/app/staff/share/circ/components.component.html index 2e17a7c2aa..123acda50a 100644 --- a/Open-ILS/src/eg2/src/app/staff/share/circ/components.component.html +++ b/Open-ILS/src/eg2/src/app/staff/share/circ/components.component.html @@ -28,6 +28,7 @@ + diff --git a/Open-ILS/src/eg2/src/app/staff/share/circ/components.component.ts b/Open-ILS/src/eg2/src/app/staff/share/circ/components.component.ts index 0736b3c8b4..921e591cd1 100644 --- a/Open-ILS/src/eg2/src/app/staff/share/circ/components.component.ts +++ b/Open-ILS/src/eg2/src/app/staff/share/circ/components.component.ts @@ -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 index 0000000000..5af11410b2 --- /dev/null +++ b/Open-ILS/src/eg2/src/app/staff/share/circ/in-transit-dialog.component.html @@ -0,0 +1,35 @@ + + + + + + + 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 index 0000000000..60098eac98 --- /dev/null +++ b/Open-ILS/src/eg2/src/app/staff/share/circ/in-transit-dialog.component.ts @@ -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); + } +} + + diff --git a/Open-ILS/src/eg2/src/app/staff/share/circ/route-dialog.component.ts b/Open-ILS/src/eg2/src/app/staff/share/circ/route-dialog.component.ts index 9328df3a16..aa90d3fb81 100644 --- a/Open-ILS/src/eg2/src/app/staff/share/circ/route-dialog.component.ts +++ b/Open-ILS/src/eg2/src/app/staff/share/circ/route-dialog.component.ts @@ -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 => { -- 2.11.0