-CHECKOUT
+<eg-prompt-dialog #nonCatCount
+ promptType="number"
+ i18n-dialogTitle dialogTitle="Non-Cat Checkout"
+ promptMin="1" promptValue="1" [promptMax]="maxNoncats"
+ i18n-dialogBody
+ dialogBody="Enter the number of {{checkoutNoncat ? checkoutNoncat.name() : ''}} circulating">
+</eg-prompt-dialog>
+
+<div class="row">
+ <div class="col-lg-12 d-flex">
+ <div class="form-inline">
+ <div class="input-group">
+ <div class="input-group-prepend">
+ <div ngbDropdown class="d-inline-block">
+ <button ngbDropdownToggle class="btn btn-outline-dark" id="checkout-button">
+ <ng-container *ngIf="!checkoutNoncat" i18n>Barcode</ng-container>
+ <ng-container *ngIf="checkoutNoncat">{{checkoutNoncat.name()}}</ng-container>
+ </button>
+ <div ngbDropdownMenu aria-labelledby="dropdownBasic1">
+ <button ngbDropdownItem (click)="checkoutNoncat=null" i18n>Barcode</button>
+ <div role="separator" class="dropdown-divider"></div>
+ <ng-container *ngIf="circ.nonCatTypes">
+ <button *ngFor="let nct of circ.nonCatTypes"
+ (click)="checkoutNoncat=nct" ngbDropdownItem>{{nct.name()}}</button>
+ </ng-container>
+ </div>
+ </div>
+ </div>
+ <input type="text" class="form-control" id="barcode-input"
+ [placeholder]="checkoutNoncat ? '' : 'Barcode...'" i18n-placeholder
+ [disabled]="checkoutNoncat != null"
+ i18n-aria-label aria-label="Barcode Input"/>
+ <div class="input-group-append">
+ <button class="btn btn-outline-dark" (click)="checkout()" i18n>Submit</button>
+ </div>
+ </div>
+ </div>
+ <div class="ml-auto">
+ here
+ </div>
+ </div>
+</div>
-import {Component, OnInit, Input} from '@angular/core';
+import {Component, OnInit, AfterViewInit, Input, ViewChild} from '@angular/core';
import {Router, ActivatedRoute, ParamMap} from '@angular/router';
+import {Observable, empty, of} from 'rxjs';
+import {tap, switchMap} from 'rxjs/operators';
import {NgbNav, NgbNavChangeEvent} from '@ng-bootstrap/ng-bootstrap';
+import {IdlObject} from '@eg/core/idl.service';
import {OrgService} from '@eg/core/org.service';
import {NetService} from '@eg/core/net.service';
import {PatronService} from '@eg/staff/share/patron/patron.service';
import {PatronManagerService} from './patron.service';
+import {CheckoutParams, CheckoutResult, CircService} from '@eg/staff/share/circ/circ.service';
+import {PromptDialogComponent} from '@eg/share/dialog/prompt.component';
@Component({
templateUrl: 'checkout.component.html',
})
export class CheckoutComponent implements OnInit {
+ maxNoncats = 99; // Matches AngJS version
+ checkoutNoncat: IdlObject = null;
+
+ @ViewChild('nonCatCount') nonCatCount: PromptDialogComponent;
+
constructor(
private org: OrgService,
private net: NetService,
+ public circ: CircService,
public patronService: PatronService,
public context: PatronManagerService
) {}
ngOnInit() {
+ this.circ.getNonCatTypes();
+ }
+
+ ngAfterViewInit() {
+ const input = document.getElementById('barcode-input');
+ if (input) { input.focus(); }
+ }
+
+ collectParams(): Promise<CheckoutParams> {
+
+ const params: CheckoutParams = {
+ patron_id: this.context.patron.id()
+ };
+
+ if (this.checkoutNoncat) {
+
+ return this.noncatPrompt().toPromise().then(count => {
+ if (!count) { return null; }
+ params.noncat = true;
+ params.noncat_count = count;
+ params.noncat_type = this.checkoutNoncat.id();
+ return params;
+ });
+ }
+
+ return null;
+ }
+
+ checkout() {
+ this.collectParams()
+
+ .then((params: CheckoutParams) => {
+ if (!params) { return null; }
+ return this.circ.checkout(params);
+ })
+
+ .then((result: CheckoutResult) => {
+ if (!result) { return null; }
+
+ // Reset the form
+ this.checkoutNoncat = null;
+ });
+ }
+
+ noncatPrompt(): Observable<number> {
+ return this.nonCatCount.open()
+ .pipe(switchMap(count => {
+
+ if (count === null || count === undefined) {
+ return empty(); // dialog canceled
+ }
+
+ // Even though the prompt has a type and min/max values,
+ // users can still manually enter bogus values.
+ count = Number(count);
+ if (count > 0 && count < this.maxNoncats) {
+ return of(count);
+ } else {
+ // Bogus value. Try again
+ return this.noncatPrompt();
+ }
+ }));
}
}
import {FmRecordEditorModule} from '@eg/share/fm-editor/fm-editor.module';
import {StaffCommonModule} from '@eg/staff/common.module';
import {HoldsModule} from '@eg/staff/share/holds/holds.module';
+import {CircModule} from '@eg/staff/share/circ/circ.module';
import {HoldingsModule} from '@eg/staff/share/holdings/holdings.module';
import {BookingModule} from '@eg/staff/share/booking/booking.module';
import {PatronModule} from '@eg/staff/share/patron/patron.module';
imports: [
StaffCommonModule,
FmRecordEditorModule,
+ CircModule,
HoldsModule,
HoldingsModule,
BookingModule,
<span class="material-icons" aria-hidden="true">person</span>
<span i18n>Search for Patrons</span>
</a>
+ <a class="dropdown-item" routerLink="/staff/circ/patron/search">
+ <span class="material-icons" aria-hidden="true">person</span>
+ <span i18n>Search for Patrons (Experimental)</span>
+ </a>
<a class="dropdown-item" href="/eg/staff/cat/item/search">
<span class="material-icons" aria-hidden="true">assignment</span>
<span i18n>Search for Items by Barcode</span>
--- /dev/null
+import {NgModule} from '@angular/core';
+import {StaffCommonModule} from '@eg/staff/common.module';
+import {HoldingsModule} from '@eg/staff/share/holdings/holdings.module';
+import {CircService} from './circ.service';
+
+@NgModule({
+ declarations: [
+ ],
+ imports: [
+ StaffCommonModule,
+ HoldingsModule
+ ],
+ exports: [
+ ],
+ providers: [
+ CircService
+ ]
+})
+
+export class CircModule {}
--- /dev/null
+import {Injectable} from '@angular/core';
+import {Observable} from 'rxjs';
+import {map, mergeMap} from 'rxjs/operators';
+import {IdlObject} from '@eg/core/idl.service';
+import {NetService} from '@eg/core/net.service';
+import {OrgService} from '@eg/core/org.service';
+import {PcrudService} from '@eg/core/pcrud.service';
+import {EventService, EgEvent} from '@eg/core/event.service';
+import {AuthService} from '@eg/core/auth.service';
+import {BibRecordService, BibRecordSummary} from '@eg/share/catalog/bib-record.service';
+import {AudioService} from '@eg/share/util/audio.service';
+
+
+export interface CheckoutParams {
+ patron_id: number;
+ noncat?: boolean;
+ noncat_type?: number;
+ noncat_count?: number;
+}
+
+export interface CheckoutResult {
+ circ?: IdlObject;
+}
+
+@Injectable()
+export class CircService {
+
+ nonCatTypes: IdlObject[] = null;
+
+ constructor(
+ private audio: AudioService,
+ private evt: EventService,
+ private org: OrgService,
+ private net: NetService,
+ private pcrud: PcrudService,
+ private auth: AuthService,
+ private bib: BibRecordService,
+ ) {}
+
+ getNonCatTypes(): Promise<IdlObject[]> {
+
+ if (this.nonCatTypes) {
+ return Promise.resolve(this.nonCatTypes);
+ }
+
+ return this.pcrud.search('cnct',
+ {owning_lib: this.org.fullPath(this.auth.user().ws_ou(), true)},
+ {order_by: {cnct: 'name'}},
+ {atomic: true}
+ ).toPromise().then(types => this.nonCatTypes = types);
+ }
+
+ checkout(params: CheckoutParams): Promise<CheckoutResult> {
+
+ console.log('checking out with', params);
+
+ return this.net.request(
+ 'open-ils.circ',
+ 'open-ils.circ.checkout.full',
+ this.auth.token(), params
+ ).toPromise().then(result => this.processCheckoutResult(result))
+ }
+
+ processCheckoutResult(response: any): Promise<CheckoutResult> {
+ console.debug('checkout resturned', response);
+
+ if (Array.isArray(response)) { response = response[0]; }
+
+ const evt = this.evt.parse(response);
+ const payload = evt.payload;
+
+ if (!payload) {
+ this.audio.play('error.unknown.no_payload');
+ return Promise.reject();
+ }
+
+ const result: CheckoutResult = {
+ circ: payload.circ
+ };
+
+ return Promise.resolve(result);
+ }
+}
+
</a>
</li>
<li>
+ <a href="/eg2/staff/circ/patron/search">
+ <span class="glyphicon glyphicon-user" aria-hidden="true"></span>
+ <span eg-accesskey-label>[% l('Search for Patrons (Experimental)') %]</span>
+ </a>
+ </li>
+ <li>
<a href="./cat/item/search" target="_self"
eg-accesskey="[% l('f5') %]"
eg-accesskey-desc="[% l('Item Status') %]">