LP1904036 Patron ui checkout tab; noncats
authorBill Erickson <berickxx@gmail.com>
Fri, 5 Feb 2021 19:58:55 +0000 (14:58 -0500)
committerGalen Charlton <gmc@equinoxOLI.org>
Fri, 28 Oct 2022 00:13:23 +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/circ/patron/checkout.component.html
Open-ILS/src/eg2/src/app/staff/circ/patron/checkout.component.ts
Open-ILS/src/eg2/src/app/staff/circ/patron/patron.module.ts
Open-ILS/src/eg2/src/app/staff/nav.component.html
Open-ILS/src/eg2/src/app/staff/share/circ/circ.module.ts [new file with mode: 0644]
Open-ILS/src/eg2/src/app/staff/share/circ/circ.service.ts [new file with mode: 0644]
Open-ILS/src/templates/staff/navbar.tt2

index d6add2b..dea9023 100644 (file)
@@ -1,2 +1,43 @@
 
-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>
index e300bbe..a372f14 100644 (file)
@@ -1,10 +1,15 @@
-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',
@@ -12,14 +17,82 @@ import {PatronManagerService} from './patron.service';
 })
 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();
+            }
+        }));
     }
 }
 
index a46313d..36e0429 100644 (file)
@@ -3,6 +3,7 @@ import {PatronRoutingModule} from './routing.module';
 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';
@@ -28,6 +29,7 @@ import {BcSearchComponent} from './bcsearch.component';
   imports: [
     StaffCommonModule,
     FmRecordEditorModule,
+    CircModule,
     HoldsModule,
     HoldingsModule,
     BookingModule,
index d707d3f..24b027f 100644 (file)
             <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>
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
new file mode 100644 (file)
index 0000000..907a85d
--- /dev/null
@@ -0,0 +1,20 @@
+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 {}
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
new file mode 100644 (file)
index 0000000..f3ab34a
--- /dev/null
@@ -0,0 +1,84 @@
+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);
+    }
+}
+
index b988653..5c59dbd 100644 (file)
             </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') %]">