-import {Injectable} from '@angular/core';
+import {Injectable, EventEmitter} from '@angular/core';
import {Router, ActivatedRoute} from '@angular/router';
import {IdlObject} from '@eg/core/idl.service';
import {OrgService} from '@eg/core/org.service';
// User object for above barcode.
holdForUser: IdlObject;
+ // Emit that the value has changed so components can detect
+ // the change even when the component is not itself digesting
+ // new values.
+ holdForChange: EventEmitter<void> = new EventEmitter<void>();
+
// Cache the currently selected detail record (i.g. catalog/record/123)
// summary so the record detail component can avoid duplicate fetches
// during record tab navigation.
if (this.holdForBarcode) {
this.patron.getByBarcode(this.holdForBarcode)
- .then(user => this.holdForUser = user);
+ .then(user => {
+ this.holdForUser = user;
+ this.holdForChange.emit();
+ });
}
this.searchContext.org = this.org; // service, not searchOrg
this.applySearchDefaults();
}
+ clearHoldPatron() {
+ this.holdForUser = null;
+ this.holdForBarcode = null;
+ this.holdForChange.emit();
+ }
+
cloneContext(context: CatalogSearchContext): CatalogSearchContext {
const params: any = this.catUrl.toUrlParams(context);
return this.catUrl.fromUrlHash(params);
-import {Component, OnInit, Input, ViewChild, Renderer2} from '@angular/core';
+import {Component, OnInit, Input, ViewChild} from '@angular/core';
import {Router, ActivatedRoute, ParamMap} from '@angular/router';
+import {tap} from 'rxjs/operators';
import {EventService} from '@eg/core/event.service';
import {NetService} from '@eg/core/net.service';
import {AuthService} from '@eg/core/auth.service';
smsCarriers: ComboboxEntry[];
smsEnabled: boolean;
+
maxMultiHolds = 0;
+
+ // True if mult-copy holds are active for the current receipient.
+ multiHoldsActive = false;
+
+ canPlaceMultiAt: number[] = [];
multiHoldCount = 1;
placeHoldsClicked: boolean;
+ badBarcode: string = null;
puLibWsFallback = false;
constructor(
private router: Router,
private route: ActivatedRoute,
- private renderer: Renderer2,
private evt: EventService,
private net: NetService,
private org: OrgService,
this.smsCarriers = [];
}
- reset() {
+ ngOnInit() {
+
+ // Respond to changes in hold type. This currently assumes hold
+ // types only toggle post-init between copy-level types (C,R,F)
+ // and no other params (e.g. target) change with it. If other
+ // types require tracking, additional data collection may be needed.
+ this.route.paramMap.subscribe(
+ (params: ParamMap) => this.holdType = params.get('type'));
- this.user = null;
- this.userBarcode = null;
this.holdType = this.route.snapshot.params['type'];
this.holdTargets = this.route.snapshot.queryParams['target'];
this.holdFor = this.route.snapshot.queryParams['holdFor'] || 'patron';
this.requestor = this.auth.user();
this.pickupLib = this.auth.user().ws_ou();
- this.holdContexts = this.holdTargets.map(target => {
- const ctx = new HoldContext(target);
- return ctx;
- });
-
this.resetForm();
- if (this.holdFor === 'staff' || this.userBarcode) {
- this.holdForChanged();
- }
+ this.getRequestorSetsAndPerms()
+ .then(_ => {
- this.getTargetMeta();
- this.placeHoldsClicked = false;
- }
+ // Load receipient data if we have any.
+ if (this.staffCat.holdForBarcode) {
+ this.holdFor = 'patron';
+ this.userBarcode = this.staffCat.holdForBarcode;
+ }
- ngOnInit() {
+ if (this.holdFor === 'staff' || this.userBarcode) {
+ this.holdForChanged();
+ }
+ });
- // Respond to changes in hold type. This currently assumes hold
- // types only toggle post-init between copy-level types (C,R,F)
- // and no other params (e.g. target) change with it. If other
- // types require tracking, additional data collection may be needed.
- this.route.paramMap.subscribe(
- (params: ParamMap) => this.holdType = params.get('type'));
+ setTimeout(() => {
+ const node = document.getElementById('patron-barcode');
+ if (node) { node.focus(); }
+ });
+ }
+
+ getRequestorSetsAndPerms(): Promise<any> {
- this.reset();
+ return this.org.settings(
+ ['sms.enable', 'circ.holds.max_duplicate_holds'])
- this.org.settings(['sms.enable', 'circ.holds.max_duplicate_holds'])
.then(sets => {
this.smsEnabled = sets['sms.enable'];
+ const max = Number(sets['circ.holds.max_duplicate_holds']);
+ if (Number(max) > 0) { this.maxMultiHolds = Number(max); }
+
if (this.smsEnabled) {
- this.pcrud.search(
+
+ return this.pcrud.search(
'csc', {active: 't'}, {order_by: {csc: 'name'}})
- .subscribe(carrier => {
+ .pipe(tap(carrier => {
this.smsCarriers.push({
id: carrier.id(),
label: carrier.name()
});
- });
+ })).toPromise();
}
- const max = sets['circ.holds.max_duplicate_holds'];
- if (Number(max) > 0) { this.maxMultiHolds = max; }
- });
+ }).then(_ => {
- setTimeout(() => // Focus barcode input
- this.renderer.selectRootElement('#patron-barcode').focus());
+ if (this.maxMultiHolds) {
+
+ // Multi-copy holds are supported. Let's see where this
+ // requestor has permission to take advantage of them.
+ return this.perm.hasWorkPermAt(
+ ['CREATE_DUPLICATE_HOLDS'], true).then(perms =>
+ this.canPlaceMultiAt = perms['CREATE_DUPLICATE_HOLDS']);
+ }
+ });
}
holdCountRange(): number[] {
}
userBarcodeChanged() {
+ const newBc = this.userBarcode;
+
+ if (!newBc) { this.user = null; return; }
// Avoid simultaneous or duplicate lookups
- if (this.userBarcode === this.currentUserBarcode) {
- return;
+ if (newBc === this.currentUserBarcode) { return; }
+
+ if (newBc !== this.staffCat.holdForBarcode) {
+ // If an alternate barcode is entered, it takes us out of
+ // place-hold-for-patron-x-from-search mode.
+ this.staffCat.clearHoldPatron();
}
this.resetForm();
+ this.userBarcode = newBc; // clobbered in reset
- if (!this.userBarcode) {
- this.user = null;
- return;
- }
-
- this.user = null;
this.currentUserBarcode = this.userBarcode;
this.getUser();
}
const promise = id ? this.patron.getById(id, flesh) :
this.patron.getByBarcode(this.userBarcode, flesh);
+ this.badBarcode = null;
promise.then(user => {
+
+ if (!user) {
+ // IDs are assumed to valid
+ this.badBarcode = this.userBarcode;
+ return;
+ }
+
this.user = user;
this.applyUserSettings();
+ this.multiHoldsActive =
+ this.canPlaceMultiAt.includes(user.home_ou());
});
}
resetForm() {
+ this.user = null;
+ this.userBarcode = null;
this.notifyEmail = true;
this.notifyPhone = true;
this.phoneValue = '';
this.pickupLib = this.requestor.ws_ou();
+ this.placeHoldsClicked = false;
+
+ this.holdContexts = this.holdTargets.map(target => {
+ const ctx = new HoldContext(target);
+ return ctx;
+ });
+
+ // Required after rebuilding the contexts
+ this.getTargetMeta();
}
applyUserSettings() {
this.patronSearch.open({size: 'xl'}).toPromise().then(
patrons => {
if (!patrons || patrons.length === 0) { return; }
-
const user = patrons[0];
-
- this.user = user;
- this.userBarcode =
- this.currentUserBarcode = user.card().barcode();
- user.home_ou(this.org.get(user.home_ou()).id()); // de-flesh
- this.applyUserSettings();
+ this.userBarcode = user.card().barcode();
+ this.userBarcodeChanged();
}
);
}