import {OrgService} from '@eg/core/org.service';
import {AuthService} from '@eg/core/auth.service';
import {PatronContextService} from './patron.service';
-
+import {CircService} from '@eg/staff/share/circ/circ.service';
@Injectable()
export class PatronResolver implements Resolve<Promise<any[]>> {
constructor(
private store: ServerStoreService,
- private context: PatronContextService
+ private context: PatronContextService,
+ private circ: CircService
) {}
resolve(
'ui.admin.patron_log.max_entries'
]).then(settings => {
this.context.settingsCache = settings;
+ return this.circ.applySettings();
});
}
}
import {CircComponentsComponent} from './components.component';
import {CircEventsComponent} from './events-dialog.component';
import {OpenCircDialogComponent} from './open-circ-dialog.component';
+import {RouteDialogComponent} from './route-dialog.component';
@NgModule({
declarations: [
PrecatCheckoutDialogComponent,
ClaimsReturnedDialogComponent,
CircEventsComponent,
+ RouteDialogComponent,
OpenCircDialogComponent
],
imports: [
import {CircEventsComponent} from './events-dialog.component';
import {CircComponentsComponent} from './components.component';
import {StringService} from '@eg/share/string/string.service';
+import {ServerStoreService} from '@eg/core/server-store.service';
export interface CircDisplayInfo {
title?: string;
copy_barcode?: string;
claims_never_checked_out?: boolean;
void_overdues?: boolean;
+ auto_print_hold_transits?: boolean;
// internal tracking
_override?: boolean;
params: CheckinParams;
success: boolean;
copy?: IdlObject;
+ volume?: IdlObject;
circ?: IdlObject;
record?: IdlObject;
+ hold?: IdlObject;
+ transit?: IdlObject;
+ org?: number;
+ patron?: IdlObject;
}
@Injectable()
suppressCheckinPopups = false;
ignoreCheckinPrecats = false;
copyLocationCache: {[id: number]: IdlObject} = {};
+ clearHoldsOnCheckout = false;
+ orgAddrCache: {[addrId: number]: IdlObject} = {};
constructor(
private audio: AudioService,
private org: OrgService,
private net: NetService,
private pcrud: PcrudService,
+ private serverStore: ServerStoreService,
private strings: StringService,
private auth: AuthService,
private bib: BibRecordService,
) {}
+ applySettings(): Promise<any> {
+ return this.serverStore.getItemBatch([
+ 'circ.clear_hold_on_checkout',
+ ]).then(sets => {
+ this.clearHoldsOnCheckout = sets['circ.clear_hold_on_checkout'];
+ });
+ }
+
// 'circ' is fleshed with copy, vol, bib, wide_display_entry
// Extracts some display info from a fleshed circ.
getDisplayInfo(circ: IdlObject): CircDisplayInfo {
};
}
+ getOrgAddr(orgId: number, addrType): Promise<IdlObject> {
+ const org = this.org.get(orgId);
+ const addrId = this.org[addrType]();
+
+ if (!addrId) { return Promise.resolve(null); }
+
+ if (this.orgAddrCache[addrId]) {
+ return Promise.resolve(this.orgAddrCache[addrId]);
+ }
+
+ return this.pcrud.retrieve('aoa', addrId).toPromise()
+ .then(addr => {
+ this.orgAddrCache[addrId] = addr;
+ return addr;
+ });
+ }
+
+ // 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
+ // the transit we want, since a transit close + open in the API
+ // returns the closed transit.
+
+ return this.pcrud.search('atc',
+ { dest_recv_time : null, cancel_time : null},
+ { flesh : 1,
+ flesh_fields : {atc : ['target_copy']},
+ join : {
+ acp : {
+ filter : {
+ barcode : result.params.copy_barcode,
+ deleted : 'f'
+ }
+ }
+ },
+ limit : 1,
+ 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()));
+ return transit;
+ });
+ }
+
getNonCatTypes(): Promise<IdlObject[]> {
if (this.nonCatTypes) {
success: success,
circ: payload.circ,
copy: payload.copy,
- record: payload.record
+ volume: payload.volume,
+ record: payload.record,
+ transit: payload.transit
};
let promise = Promise.resolve();;
const copy = result.copy;
+ const volume = result.volume;
if (copy) {
if (this.copyLocationCache[copy.location()]) {
}
}
+ if (volume) {
+ // Flesh volume prefixes and suffixes
+
+ if (typeof volume.prefix() !== 'object') {
+ promise = promise.then(_ =>
+ this.pcrud.retrieve('acnp', volume.prefix()).toPromise()
+ ).then(p => volume.prefix(p));
+ }
+
+ if (typeof volume.suffix() !== 'object') {
+ promise = promise.then(_ =>
+ this.pcrud.retrieve('acns', volume.suffix()).toPromise()
+ ).then(p => volume.suffix(p));
+ }
+ }
+
return promise.then(_ => result);
}
case 7: /* RESHELVING */
this.audio.play('success.checkin');
return this.handleCheckinLocAlert(result);
+
+ case 8: /* ON HOLDS SHELF */
+ this.audio.play('info.checkin.holds_shelf');
+
+ const hold = result.hold;
+
+ if (hold) {
+
+ if (hold.pickup_lib() === this.auth.user().ws_ou()) {
+ this.components.routeDialog.checkin = result;
+ return this.components.routeDialog.open().toPromise()
+ .then(_ => result);
+
+ } else {
+ // Should not happen in practice, but to be safe.
+ this.audio.play('warning.checkin.wrong_shelf');
+ }
+
+ } else {
+ console.warn("API Returned insufficient info on holds");
+ }
}
return Promise.resolve(result);
i18n-dialogBody dialogBody="This item needs to be routed to CATALOGING">
</eg-alert-dialog>
-<eg-string key="staff.circ.checkin.location.alert"
- i18n-text text="Item {{barcode}} needs to be routed to {{location}}">
+<ng-template #locAlertTemplate let-barcode="barcode" let-location="location" i18n>
+ Item {{barcode}} needs to be routed to {{location}}.
+</ng-template>
+<!-- dialogBody is generated dynamically from string above -->
+<eg-string key="staff.circ.checkin.location.alert" [template]="locAlertTemplate">
</eg-string>
-<!-- dialogBody is generated dynamically from string above -->
<eg-alert-dialog #locationAlertDialog i18n-dialogTitle dialogTitle="Route Item">
</eg-alert-dialog>
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';
/* Container component for sub-components used by circulation actions.
*
@ViewChild('routeToCatalogingDialog') routeToCatalogingDialog: AlertDialogComponent;
@ViewChild('openCircDialog') openCircDialog: OpenCircDialogComponent;
@ViewChild('locationAlertDialog') locationAlertDialog: AlertDialogComponent;
+ @ViewChild('routeDialog') routeDialog: RouteDialogComponent;
constructor(private circ: CircService) {
this.circ.components = this;
--- /dev/null
+<ng-template #dialogContent>
+ <div class="modal-header bg-info">
+ <h4 i18n>Route Item to Holds Shelf</h4>
+ <button type="button" class="close"
+ i18n-aria-label aria-label="Close" (click)="close()">
+ <span aria-hidden="true">×</span>
+ </button>
+ </div>
+ <div class="modal-body">
+ </div>
+ <div class="modal-footer">
+ <button type="button" class="btn btn-success" (click)="close()" i18n>Submit</button>
+ <button type="button" class="btn btn-warning" (click)="close()" i18n>Cancel</button>
+ </div>
+</ng-template>
--- /dev/null
+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 {CircService} from './circ.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 {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: 'components.component.html',
+ selector: 'eg-circ-components'
+})
+export class RouteDialogComponent extends DialogComponent {
+
+ checkin: CheckinResult;
+ noAutoPrint: {[template: string]: boolean} = {};
+ slip: string;
+ orgAddress: IdlObject;
+ destCourierCode: string;
+ destOrg: IdlObject;
+
+ constructor(
+ private modal: NgbModal,
+ private pcrud: PcrudService,
+ private org: OrgService,
+ private circ: CircService,
+ private audio: AudioService,
+ private print: PrintService,
+ private serverStore: ServerStoreService) {
+ super(modal);
+ }
+
+ open(ops?: NgbModalOptions): Observable<any> {
+
+ return from(this.applySettings())
+
+ .pipe(concatMap(exit => {
+ if (exit) {
+ return of(exit);
+ } else {
+ return from(this.collectData());
+ }
+ }))
+
+ .pipe(concatMap(exit => {
+ if (exit) {
+ return of(exit);
+ } else {
+ return super.open(ops);
+ }
+ }));
+ }
+
+ collectData(): Promise<boolean> {
+
+ let promise = Promise.resolve(null);
+ const hold = this.checkin.hold;
+
+ if (this.checkin.org && this.slip !== 'hold_shelf_slip') {
+
+ promise = promise.then(_ => {
+ return this.circ.getOrgAddr(this.checkin.org, 'holds_address')
+ .then(addr => this.orgAddress = addr);
+ });
+ }
+
+ if (hold) {
+
+ promise = promise.then(_ => {
+ return this.pcrud.retrieve('au', hold.usr(),
+ {flesh: 1, flesh_fields : {'au' : ['card']}}).toPromise()
+ .then(patron => this.checkin.patron = patron);
+ });
+ }
+
+ if (this.slip !== 'hold_shelf_slip') {
+
+ promise = promise.then(_ => this.circ.findCopyTransit(this.checkin))
+ .then(transit => {
+ this.checkin.transit = transit;
+ return this.org.settings('lib.courier_code', transit.dest.id())
+ .then(sets => this.destCourierCode = sets['lib.courier_code']);
+ });
+ }
+
+ if (this.checkin.transit) {
+ this.destOrg = this.org.get(this.checkin.transit.dest());
+ }
+
+ this.audio.play(hold ?
+ 'info.checkin.transit.hold' : 'info.checkin.transit');
+
+ if (this.checkin.params.auto_print_hold_transits
+ || this.circ.suppressCheckinPopups) {
+ // Print and exit.
+ return this.printTransit().then(_ => false);
+ }
+
+ return promise;
+ }
+
+ applySettings(): Promise<boolean> {
+
+ if (this.checkin.transit) {
+ if (this.checkin.patron) {
+ this.slip = 'hold_transit_slip';
+ } else {
+ this.slip = 'transit_slip';
+ }
+ } else {
+ this.slip = 'hold_shelf_slip';
+ }
+
+ const autoPrintSet = 'circ.staff_client.do_not_auto_attempt_print';
+
+ return this.serverStore.getItemBatch([autoPrintSet]).then(sets => {
+ const autoPrintArr = sets[autoPrintSet];
+
+ if (Array.isArray(autoPrintArr)) {
+ this.noAutoPrint['hold_shelf_slip'] =
+ autoPrintArr.includes('Hold Slip');
+
+ this.noAutoPrint['hold_transit_slip'] =
+ autoPrintArr.includes('Hold/Transit Slip');
+
+ this.noAutoPrint['transit_slip'] =
+ autoPrintArr.includes('Transit Slip');
+ }
+ })
+ .then(_ => this.noAutoPrint[this.slip]);
+ }
+
+ printTransit(): Promise<any> {
+ return null;
+ }
+}
+